First release under the fork-marker convention: VERSION_TAG carries a trailing
`M`, so /api/version, the PS5 start-up notification, the stdout banner, the UI
footer and the ELF file name all read "v1.9.3M" in one move -- and a fork build
can no longer collide with an upstream artifact of the same version, a mix-up
that already happened twice. The footer carries a tooltip spelling the marker
out.
Two bodies of work.
1. Encrypted archives (ZIP / RAR / 7z)
- ZIP: ZipCrypto (traditional PKWARE) and WinZip AES-256, through
minizip-ng plus a vendored crypto layer (mz_crypt_wfm.c,
mz_strm_pkcrypt.c, mz_strm_wzaes.c).
- RAR: RARSetPassword, wired after RAROpenArchiveEx and before the first
RARReadHeaderEx. The ordering is load-bearing, not stylistic.
- 7z: 7zAES including -mhe=on encrypted headers, via a virtual
ISeekInStream that splices a pseudo-header + the real archive + the
decrypted header, so no offset stored inside the archive has to move.
A wrong password is reported as ZIPX_ERR_PASSWORD, and a failed attempt
leaves no staging directory behind.
2. Reporting, and the UI round that on-device testing produced
- A RAR whose dictionary exceeds what the build supports now gets its own
extract_dict_too_large code instead of being mis-reported as "entry too
large"; the message names both the required and the supported size. The
behaviour is deliberately unchanged -- such archives are still refused,
because admitting one means allocating the whole window up front, which
is why rarlab's own CLI refuses them by default.
- The upload entry is a menu again: one "Upload" button opening "Upload
files / Upload folder". The previous main-button-plus-small-arrow made
"upload folder" effectively undiscoverable.
- A drag-and-drop hint sits in the footer (hidden on the console browser,
where drag is not how anyone uploads).
- The extract button is now always present and merely disabled until
exactly one archive is selected, instead of appearing out of nowhere.
- Upload-and-extract on an encrypted archive now prompts for the password
directly. The retry table used to be keyed by PATH, and for a non-ASCII
directory the string the page holds and the string the server reports
are not the same bytes -- the lookup missed, so the user got a bare
error box and had to press Extract by hand before the prompt appeared.
It is now keyed by task id, which the server assigns and echoes back
verbatim.
- Error text passes through decodeFsText(), so a GBK entry name no longer
surfaces as `â®…ç§.psd`.
- Local names are encoded with encodeFsText() before being joined onto a
server-side path. fs_path_value() declines to rewrite a path if ANY code
point exceeds 0xFF, so concatenating a local name onto a server directory
produced a mixed representation and a silently dead path.
- The menu row highlight was losing the cascade to the generic button rule
(identical specificity, later in the file) while inheriting the toolbar's
3px focus ring, which overflowed a 46px row. Both rules are now scoped to
the panel and the keyboard cue is an inset ring, so it cannot escape the
row at any line height.
- The footer status line is clamped to a single line; a long
"uploading 3/12: some-name.zip" used to wrap out of the 46px footer.
- A first failed password attempt now says the archive is encrypted,
instead of blaming a password the user was never asked for.
Artifact
web-file-mgr-v1.9.3M.elf
903,448 B
sha256 8ca47d5aaca75085b32641300cce30fadb7df7749cb6b53d04f129bcecc286b7
e_machine 0x003e (x86-64 / PS5)
The file size is identical to the four builds before it, and every one of
them carries a different sha256: only .rodata moved, and by less than the
16 KiB section alignment absorbs. Compare sections with `readelf -SW` --
never infer "nothing changed" from the byte count.
Verification
- host suites: 140 ZIP + 37 RAR = 177 checks, 0 failures
- 7z suite: 27 cases, 0 failures. The `aeshe` entry that used to sit in
KNOWN_GAPS is gone -- the -mhe=on fixture now passes both the folder
decoder and the extraction facade
- frontend: .build/ui_retry_test.mjs (40 checks), .build/ui_upload_menu_test.mjs
(40 checks), .build/preview_check.mjs (12 assertions in headless Chromium
against the real page and a fixture API). Two layout regressions and the
highlight cascade bug were caught by the last one and by nothing else --
reading the source, both CSS rules "look correct"
- built twice from this tree: byte-identical (cmp clean). rsync refreshes
every asset mtime, so this is a genuine recompile, not make short-circuiting
on unchanged sources
- embedded assets verified in place with .build/check-elf-gzip.py, because
gen-asset-module.py gzips them and plain `strings` finds none of their text
- exercised end to end on a real PS5; the checklist is
docs/DEVICE-TEST-v1.9.3M.md
Docs
- docs/USER-GUIDE-zh-CN.md (new, simplified Chinese user guide)
- docs/DEVICE-TEST-v1.9.3M.md (new, on-device acceptance checklist)
- docs/REAL-CONSOLE-PROFILE.md (new, measured console behaviour)
- docs/archive/HANDOVER-v1.8-planning.md (superseded v1.8 design notes)
- CHANGELOG / README (both languages) / HANDOVER updated with the artifact
fingerprint, the section deltas and the new test counts
19 KiB
真机验证清单:解压速度(10–40 MB/s 到底卡在哪)
⚑ 本清单的终局:停在第 1 步之前(2026-09-23 18:15,用户决定)
T_copy没做;RAR 多线程(#51)不做;生产代码一行不动、不刷机 —— 11 分钟维持现状。理由:MT 的收益在 PC 上已实测(下面「第三件」2.45×/我们引擎 1.40–1.74×), 但能不能兑现,刚好吊在本文档第 1 步那个实验上 —— 而 MT 只并行解码、写盘恒为主线程 串行(
unpack50mt.cpp的UnpWriteBuf()只在主线程调,见 §六 终局块), 所以那个实验的结果很可能是「收工」。赌一次的成本(改构建 + 刷机 + 重跑 11 分钟 + 真机验证) 压在不确定收益上 ⇒ 不做。下面全部内容保留,作为「将来重开时的现成答案」。 ⚠️ 重开的第一个动作是第 1 步的
T_copy,不是改构建。
⚠️⚠️ 2026-09-23 深夜(第二次更正):归档参数已拿到、MT 已实测、最大嫌疑已排除
口径修正:包不是 18 GB,是 3 个分卷 ≈ 11.6 GB(4 GB + 4 GB + 3.6 GB)。 下文凡写「18 GB」处,一律按「≈11.6 GB」读;核心算术已在本块重算。
① 真实归档参数(从 part3 的头实测,该文件随后被清理) RAR 5、卷 3/3、锁定、不是固实(直解主头
archive flags=0x0013,MHFL_SOLID=0x0004未置位)、 压缩参数-m1 -md=4g(最快档 + 4 GiB 字典)。关键条目PPSA16608.exfat19.49 GB → 打包 3.83 GB(ratio 5.09),并整段都在 part3 内。 详见docs/EXTRACTION-PERF.md§六 第二次更正块。② 最大嫌疑(scan 白解码一遍 = 2×)已排除。 那建立在「固实 skip 必须解码」 (
dll.cpp:341只在!Arc.Solid走廉价SeekToNext())之上;本档非固实 ⇒ 我们的 scan 不解码。③ MT 收益已实测 = ~2.4×(真实形状夹具:
-m1 -md4g、5:1 可压缩、2 GB、-mt1386→-mt8946 MB/s 解压后)。 接线只需 1 个编译开关 + 1 个链接开关(-DRAR_SMP+-lpthread),不必改 vendored 源码、 不必增删源文件 —— 早期版本写的「还要加threadmisc.cpp」是错的:threadpool.cpp:5已经#include "threadmisc.cpp"(实测nm unrar7_threadpool.o里有T GetNumberOfThreads),再加会重符号。④ 但矛头现在指向「PS5 写路径」——所以本清单第 1 件事(
T_copy)不变,而且更该先做。
- 单线程解码在 PC 上同形状就是 386–505 MB/s;PS5 单核按 1/3 算也 ~130 MB/s。
- 真机:只算
.exfat一项就是 19.49 GB ÷ 660 s = ≥29.5 MB/s 解压后。- 两次独立操作撞同一个数:上传(写 11.6 GB)30–40 MB/s = 解压(写 ≥19.5 GB)≈30 MB/s。 ⇒ 优先怀疑这条写路径的上限就是 30–40 MB/s。若
T_copy也 ~10 分钟 ⇒ 收工,MT 不做 (做了会被 I/O 吃掉);若T_copy明显快 ⇒ 立刻上 MT,那 2.4× 是真的。下面是原始判断(保留),优先级以本块为准。
⚠️ 2026-09-23 晚 更正 —— 本文档的起点判断已反转。
本文最初的前提是「已排除解码器」,那建立在两条现已作废的论证上: ① 拿 PS5 上解 RAR 的 28 MiB/s 去比 PC 上解 7z 的 427 MiB/s(不同格式/解码器/机器); ② UI 速度的 4× 摆动(已自行降级为 250 ms 采样噪声的弱证据)。
新增的三条事实把方向翻了过来:格式是 RAR(解码就是 rarlab 的 UnRAR 库本身); 包原本在 PC 上、经插件上传进 PS5;上传实测 30–40 MB/s。随后查代码发现: 我们的 POSIX 构建没有定义
RAR_SMP(os.hpp:43-45的#define RAR_SMP在#ifdef _WIN_ALL内;官方 POSIX makefile 第 11 行是DEFINES=... -DRAR_SMP), 后果是unpack50mt.cpp(Unpack::Unpack5MT,多线程 RAR5 解压器)根本没编进来,unpack.cpp的 MT 分支整段不参与编译 ⇒ RAR 解码在 PS5 上是单线程的。⇒ 新的首要假设:28 MiB/s ≈ 单线程 RAR5 解码的正常量级。 详细更正与代码行号见
docs/EXTRACTION-PERF.md§六 文首更正块。 下文按「实测 → 判读」仍有效;但第 2 步(读粒度)对 RAR 不适用,已加注。
已到手的数据(2026-09-23 真机)
| 项 | 值 |
|---|---|
| 包大小 | ≈11.6 GB 压缩包(4 GB + 4 GB + 3.6 GB 三个分卷;✅ 2026-09-23 晚更正,原写 18 GB 有误) |
| 条目数 | 1252 |
| 总耗时 | 11 分钟 = 660 s(UI 显示) |
| UI 速度口径 | 解压后字节(src/rar_extract.c:766 在 UCM_PROCESSDATA 里 bytes_done += p2)⇒ 10–40 MB/s 是「吐出数据的速度」 |
| 平均吞吐(输入侧) | ≥ 17.6 MB/s 打包字节(11.6 GB ÷ 660 s) |
| 平均吞吐(输出侧) | ≥ 29.5 MB/s 解压后——只算 PPSA16608.exfat 一项就是 19.49 GB ÷ 660 s |
| 格式 | RAR 5 ✅(头 8 字节 52 61 72 21 1A 07 01 00) |
| 固实 | 不是固实 ✅(主头 MHFL_SOLID=0x0004 未置位) |
| 压缩参数 | -m1 -md=4g(最快档 + 4 GiB 字典)✅ |
| 关键条目 | PPSA16608.exfat 19 493 027 840 B → 打包 3 831 295 727 B(ratio 5.09),整段在 part3 内 |
| 来源 | 包原本在 PC 上,经插件本身上传进 PS5,上传速度 30–40 MB/s(写 11.6 GB) |
| 源 / 目标设备 | 待确认 ⬅ 现在最关键的一条(内置 SSD / 外置 USB / 是否同盘) |
| 解压后总大小 | 待确认 ⬅ 决定输出侧吞吐的确切值(≥19.49 GB 已知) |
「18 GB 是压缩包大小」这条把口径钉住了,而且给出一个不需要知道解压后大小的硬上界:
源盘必须在 660 s 内交出 18 GB 的归档数据
⇒ 整条流水线的平均吞吐上界 = 18 GB ÷ 660 s ≈ 28 MiB/s
解压后有多大都不影响这个上界 —— 无论解码多快,源盘就只有这个交付速度。
所以第 1 步的 T_copy 和它可以直接比大小(同一个 18 GB 文件、同一对设备)。
两个立刻可用的推论:
- per-entry(小文件)开销解释不了它。 1252 个文件、平均 14.7 MB,不是「几万个小文件」那种 形态;建文件 + rename 这两种 per-entry 成本加起来分摊到 0.53 s/文件 里微乎其微。
- UI 上那个 10–40 MB/s 是瞬时值,不是平均值。 进度回调只在累计 ≥ 1 MiB 时上报
(
src/zip_extract.c:123),src/task.c:202-206每 250 ms 采一次样。250 ms 窗口 + 写缓冲突发会让速度在「忽停忽走」之间跳,波动里含相当比例的采样噪声。 ⇒ 只有「解压后字节 ÷ 总耗时」可信;那 4× 的摆动只能说明「不是恒定负载」,不能单独定案。
★ 你要在真机上做的事(就两件)
第一件:补一个数字 T_copy(3 分钟操作 + 一次等待)
- 把那个 18 GB 的包,用插件自己的复制功能,复制到解压时输出所在的那块盘
- 记下耗时(UI 上有)
- 顺便记下:这个包原本在哪(内置 SSD / 外置 USB),解压输出到哪(同盘还是另一块)
判读(就在 T_copy 与 660 s 之间比):
T_copy |
结论 | 我接下来做什么 |
|---|---|---|
| ≥ 10 分钟 | 存储物理极限,与代码无关 | 收工,解码优化全部关闭(#48 取消) |
| 1–2 分钟 | 解压比同一对设备上的纯搬运慢 5–10× ⇒ 代码里有真问题 | 做 #48:vol_read() 加 read-ahead |
| 介于中间 | 混合 | 按字节数扣掉 I/O 分量,差值才是能改的部分 |
为什么这个数字这么关键:它是同一台机器、同一对设备、同一份代码,只把「解压」换成 「纯搬运」。不需要造新包、不需要第二块盘、不需要任何假设。上面那 4 个对照实验(第 3 步) 只在它的结论模糊时才需要。
第二件:两个小事实 —— 已答一半,剩两个待确认
RAR4 还是 RAR5?RAR5 ✅;是否固实?非固实 ✅;压缩参数-m1 -md=4g✅ (都从 part3 的头上直接读到,见文首第二次更正块 ①)。 顺带确认:UI 的口径是解压后字节(src/rar_extract.c:766)。- 还缺两个数(都很便宜):
- 解压后总大小 —— 决定输出侧吞吐。已知 ≥19.49 GB(仅
.exfat一项)。 插件跑解压时 UI 上的total就是它(task->total = p->bytes_total,src/extract.c:64)。 - 源 / 目标设备 —— 包在哪块盘、解压输出写到哪块盘、是否同一块盘。 这一条现在比什么都重要:写路径被怀疑是墙(文首块 ④)。
- 解压后总大小 —— 决定输出侧吞吐。已知 ≥19.49 GB(仅
第三件(我已做完,不用上真机):多线程 RAR5 的收益
用 WinRAR 自带的 UnRAR 7.23(-mt<N> 开关可解析,虽然不出现在 -? 帮助里)
在一份按真实形状造的夹具上做 A/B(-m1 -md4g、5.27:1 可压缩、2 GB、分卷):
| 线程 | 耗时 | 解压后吞吐 | 加速 |
|---|---|---|---|
| 1 | 5.18 s | 386 MB/s | 1.00× |
| 4 | 2.39 s | 835 MB/s | 2.16× |
| 8 | 2.11 s | 946 MB/s | 2.45× |
⇒ 收益 2.4×,且已在真实形状上验证(非固实版本 2.42×,量级一致)。
接线只需 1 个编译开关 + 1 个链接开关(-DRAR_SMP / -lpthread),不用改 vendored 源码、
不用增删源文件(早期写的 threadmisc.cpp 那一条经实测作废,见 §六 块 ④ 行内更正)——
原因与行号见 docs/EXTRACTION-PERF.md §六 第二次更正块 ④。
⚠️ 但结论顺序仍以第一件为准:先
T_copy。如果墙是 30–40 MB/s 的写路径, 这 2.45× 会被 I/O 完全吃掉,做了等于白做。 ⇒ 闭环(2026-09-23 18:15):T_copy没做,用户决定不做这项了(见文首终局块)。 本节的实测数据保留 —— 将来重开时它就是收益依据;但第一步仍是T_copy。
第 1 步(决定性实验,= 上面「第一件」):用插件自己的「复制」功能做 I/O 基线
这是目前唯一能把「存储」和「我们的代码」一刀切开的实验,而且零改动。
插件里 TASK_COPY 是已实现功能,并且对 ≥ 256 MiB 的文件自动走
copy_file_pipeline()(src/filemgr.c:836,阈值见 :37):3 个 slot × 8 MiB、4096 字节对齐、
独立读线程 + 独立写线程。也就是说,它就是「同一台 PS5、同一对设备、同一份代码,
只把解压那一步换成纯搬运」。
操作:把那个 18 GB 的包,用插件自己的复制功能,复制到解压时输出所在的那块盘上。
记下耗时 T_copy。
判读:
| 观察到 | 结论 | 下一步 |
|---|---|---|
T_copy ≈ 10 分钟或更多 |
存储物理极限,与我们的代码无关 | 收工。解码优化全部搁置 |
T_copy ≈ 1–2 分钟(远小于 660 s) |
我们的解压比同一对设备上的纯 I/O 慢 5–10× ⇒ 代码里有真问题 | 进第 2、第 4 步 |
| 介于两者之间 | 混合 | 按字节数把 I/O 分量扣掉,差值才是我们能动的部分 |
严格比较要看总移动字节:复制读 18 GB + 写 18 GB = 36 GB;解压读 ≤ 18 GB、写 = 解压后大小。 所以「复制 36 GB 用了多久」和「解压搬了 (18 GB + 解压后大小) 用了 660 s」才是同一口径。
为什么这个实验优于第 3 步那一堆:它不需要造新包、不需要两块盘、不需要任何假设, 而且测的就是出事的那对设备。第 3 步只在第 1 步结论模糊时才需要。
第 2 步:读写粒度审计 —— 目前唯一可疑的代码级病因(⚠️ 对 RAR 不适用)
2026-09-23 晚加注:本节整段只对 7z / ZIP 有效。 它比较的是「引擎内部缓冲大小」, 而 RAR 的归档 I/O 在 UnRAR 自己的
File类里(我们按路径打开,src/rar_extract.c:1059),我们的回调只收到解压后的数据 —— 读粒度我们改不了。 唯一还能对上 RAR 的那半句是「写入粒度」,而 RAR 的写出也在 UnRAR 内。 ⇒ 真机工作负载是 RAR 时,本节没有可操作性;留着是为了 7z/ZIP 场景。 顺带:本表里 RAR 那行写的 4 MiB 是File::CopyBufferSize()(third_party/unrar7/file.hpp:148-153),那是文件复制的缓冲,不是解压读取缓冲 —— 原文引用错了行,这条勘误一并记在这里。
把四条路径的单次请求大小摊开看(已核对源码):
| 路径 | 源侧读粒度 | 目标侧写粒度 |
|---|---|---|
| 复制(pipeline,≥ 256 MiB) | 8 MiB × 3 slot,4096 对齐,独立读线程 | 8 MiB |
| 7z | chain SZ_IN_CHUNK = 256 KiB(src/sevenz_chain.c:87);MT 路径 inBufSize_MT = 1 MiB |
SZ_OUT_CHUNK = 64 KiB(src/sevenz_chain.c:94) |
| ZIP | ZIPX_IO_BUFFER = 128 KiB(src/zip_extract.c:32) |
128 KiB(src/zip_extract.c:900) |
| RAR | UnRAR 内部 File::CopyBufferSize() = 4 MiB(third_party/unrar7/file.hpp:148-153) |
4 MiB |
⇒ 结论一:三个引擎都不是「小读」病理,最小也有 128 KiB,不是 4 KB 那种灾难。
⇒ 结论二:但都比复制小 32–64 倍。
为什么这可能正是病根 —— 在延迟主导的设备上,吞吐 ≈ 单次请求大小 ÷ 每次请求的等效延迟:
256 KiB / 10 ms = 25 MB/s ← 正好落在实测 10–40 MB/s 的中间
8 MiB / 10 ms = 800 MB/s ← 复制路径不会撞这个上限
这个算术顺带解释两件事:
- 为什么吞吐会 4× 摆动:等效延迟随设备状态(HDD 寻道、USB 桥接、写缓存回刷)变化, 线性映射到吞吐上就是大幅波动。
- 为什么「条目平均 14.7 MB」没能救我们:per-entry 成本的确不是主因,但读请求粒度是另一回事 —— 它由引擎内部缓冲决定,与条目大小无关。
最可能的场景是源和目标在同一块盘(外置 USB HDD 上解压到同一块盘):读流和写流同时存在, 磁头来回跑,OS read-ahead 被写回刷反复打断,于是每次请求退化成一次寻道 —— 这正好让上面那个 算术成立。第 1 步如果出现「复制明显快于解压」,就是在支持它(复制的寻道次数只有解压的 1/32)。
如果第 1 步指向这里,改动面其实很小:所有 7z 的读都只经过 src/sevenz_volstream.c 的
vol_read() 这一个函数(ZIP/RAR 同理在 src/zipx_volstream.c)。在那里加一层 read-ahead
(向后 seek 时丢弃缓存)即可,不需要动解码器。但这要等第 1 步的结论,现在不写。
第 3 步:四个对照实验(零改动)—— 第 1 步结论模糊时才需要
| 实验 | 做法 | 若结果为 A | 若结果为 B |
|---|---|---|---|
| A 换简单包 | 造一个「单一大文件(如 10 GB 伪随机数据)」的 zip,放内置 SSD,解到内置 SSD | 吞吐跳到 150+ MiB/s ⇒ 慢在条目数 / per-entry 开销 | 仍是 10–40 ⇒ 慢在存储或 I/O 模式,与条目数无关 |
| B 换源设备 | 同一个包,分别放内置 SSD 与外置 USB | 两者差别巨大 ⇒ 就是源盘带宽 | 两者一样慢 ⇒ 不是源盘 |
| C 换目标设备 | 同一个包,分别解到内置与外置 | 差别巨大 ⇒ 写入端是瓶颈 | 一样慢 ⇒ 不是目标盘 |
| D 同盘 vs 异盘 | 包与输出在同一块盘 / 在两块盘 | 同盘明显更慢 ⇒ 读写争用(这也是第 2 步最看好的假设) | — |
参考量级:机械/低成本 USB HDD 顺序读写约 30–80 MB/s,小文件随机访问降到 1–10 MB/s。
第 4 步:只有在第 1/3 步指向「我们的代码」时才做(要改代码)
- 阶段计时:在四个阶段边界累加时间戳 ——
scan/read+decode/write staging/publish。用clock_gettime(CLOCK_MONOTONIC, ...)(该原语已在三个引擎里用于进度上报, 真机可用)。收尾用printf打进日志。 - 拆开
read+decode:这一段目前分不开。可用「同一归档跑两遍(第二遍吃页缓存)」或 「解到 /dev/null 类目标」来逼近 I/O 与解码的分界。 SZX_MT_THREADS运行时可配:现在是src/sevenz_mt.h:21的编译期#define 8, 做线程数 A/B 必须重编四次。改成运行时读(默认仍 8)会让实验便宜很多。- 读粒度 A/B:
SZ_IN_CHUNK/ZIPX_IO_BUFFER加大到 1–4 MiB 各构建一版, 看第 1 步指出的那条曲线是否真的跟着动。 - 埋点要用编译开关控制,关闭时零开销 —— 避免影响已发布的产物指纹。
已排除的项(不要再花时间)
⚠️ 本表 2026-09-23 晚已按「格式 = RAR」重排。原表里「解码整体就不是瓶颈」这条前提已撤回, 所以同一批项现在被分成两类:与 RAR 无关(不用看)、以及真已排除。两类都不要花时间。
A. 与 RAR 工作负载无关(它们是 7z / ZIP 的项;RAR 解码在 rarlab 库里,我们碰不到)
| 项 | 为什么无关 |
|---|---|
| CRC 硬件化 | 对 7z/ZIP 才值 4–5%;RAR 的 CRC 由 UnRAR 自己算 |
| MT 扩到 BCJ2 / 多 folder | 纯 7z 概念 |
| ZIP inflate 换 libdeflate | ZIP 专属 |
| AES-NI | 只影响 ZIP 加密流与 7zAES;RAR 加密走 UnRAR 自己的 rijndael |
| 读粒度 / read-ahead(#48) | RAR 按路径自读(src/rar_extract.c:1059 的 od.ArcName),回调只收解压后数据,插不进去 |
B. 真已排除(与格式无关,或已结论)
| 项 | 为什么排除 |
|---|---|
| 与 7-Zip 比倍数 | 真机上没有可比对象(上游那个 wfm-7zip-helper.elf 单独分发,本仓刻意不走 helper 路线);而且那是 7z 的对比,与 RAR 负载无关 |
| 逐条目 fsync | 已经全部移除(2026-09-16) |
| 「条目太小」 | 1252 条 / 18 GB,平均 14.7 MB,不是小文件场景 |
| 「我们的 RAR 解码器写得慢」 | 解码就是 vendored 的 UnRAR 库本体,我们只剩一层 facade |
C. 唯一还站着的、且直接针对真实负载的一项 ⬅ 优先看这个
| 项 | 状态 |
|---|---|
UnRAR RAR5 多线程解压(RAR_SMP + -lpthread) |
❌ 不做(2026-09-23 用户决定,见文首终局块)。我们没打开它;收益已实测(PC 上 2.45× / 我们引擎 1.40–1.74×)但被「写路径是否为墙」卡住,而验证它的 T_copy 未执行 ⇒ 搁置 |