FAQ

性能、格式、未来格式,以及 lha-rs 问题。 这是一份对静态二进制分发背后分析的汇总记录。

解压速度 · tANS 竞争区间 · zstd -1 是「最佳」吗 · lha-rs · lha vs bzip2 / 7z · 新格式放哪里

为什么 lha 的解压比 deflate / zstd / lz4 慢这么多?

简短回答:算法本质是位串行的,源码里完全没有 SIMD。 通过非 SIMD 的算法优化可以获得 3-7× 加速;要追上 zstd -1 是多年的工程。

源码里的两条热点路径

解码 LZHUF 意味着:

为什么这从根本上就慢

不改磁盘格式就能做的优化

优化改在哪里解压收益
预计算的符号查找表(zlib-style inflate) 替换 src/huf.c 里的逐位树走 3-5×(位循环部分)
memcpy(dst, src, len) 用于 LZ77 反向引用复制 替换 src/slide.c 的逐字节循环 2-4×(复制部分)
去分支的 16 位表查找 同 #1,更激进 5-7×
两阶段流水线(读 buffer + 分块解码) 重接 src/dhuf.csrc/slide.c 1.5-2×
缓存热反向引用距离(top-3 MRU) 新加在 src/slide.c 1.2-1.5×

合计:3-7× 解压加速,把 lha -lh5- 从 ~45 MB/s 提到 ~150-300 MB/s(kenlm 语料)——和 deflate level 1-3(同语料约 300-500 MB/s)有竞争力,但 "同类最强"还差得远。

硬上限:tANS 能买的和不买的

要追到 zstd -1(~2 GB/s),需要根本不同的熵编码器 (tANS/FSE)和更快的 LZ77。也就是改磁盘格式——见下面 "新格式放哪里?"。 今天的 lha -lh5- C 解码器是上限;LZHUF 内部没有任何 算法能突破 ~300 MB/s 解压。

来源

数据来自 5 次跑、kenlm 上游源码(1.83 MiB / 304 文件), Apple M 系列,原生 lha 编译。完整表格见 性能页。5 维源码审计见 安全页,确认位循环和逐字节 复制是仅有的两条解压热点路径。

tANS 能让 lha 和现代压缩器有竞争力吗?

简短回答:能。配上 tANS,lha 进入和 deflate / brotli 同一档; 但到不了 zstd -1+ 的档次(多年优化差距), 也到不了 lz4 / snappy 的档次 (根本是不同架构)。

tANS 在每个对手面前的定位

对手解压速度 tANS 为什么有 / 没有用
zstd -1~2 GB/s Yann Collet 在 FSE/tANS 上调优 8+ 年。"第一版" tANS 实现能到 ~1-1.5 GB/s,比 zstd -1 慢 30-50%。 多年迭代才能追上。
zstd -3+~1 GB/s 差距更大;optimal parser 是难点。
deflate -1 ~ -3500-800 MB/s 同一档。tANS 能到 ~500-800 MB/s,deflate 也能。差距在小的常数因子内。
brotli -1300-500 MB/s 略领先——brotli 用静态 Huffman,新 tANS 实现能快一点点(同样压缩率下)。
lz4 / snappy / LZO3-5 GB/s 永远追不上。这些没有熵编码器——它们把 LZ77 匹配存成 4 字节 token,解压 ~1 byte/cycle。 LHA-with-tANS 多了 lz4 没有的那层,永远追不到 那个速度档位(架构差距)。

硬上限

解码器被内存带宽 + 分支预测失误 + 缓存未命中三者 限制。tANS 把分支预测失误降到最低(每个符号一次分支)。 lz4 把缓存未命中降到最低(16 字节一块的 literal 复制)。 现代 CPU 跑最简单的解码器能到 ~1 byte/cycle;tANS 大约 1-2;lz4 大约 1。两种架构交换这两个瓶颈, 彼此从外部都压不死对方。

LHA 的实际竞争定位

加 tANS 给 lha 的是"和 deflate / brotli 同档",不是 "打败 zstd -1+",更不是"打败 lz4"。LHA 在 2026 年 的真正价值不是速度——而是 35 年格式稳定性 + 单文件零依赖静态二进制。 速度是那个承诺的代价。问题变成:我们是加 tANS 作为 新格式.lh8- 给追求速度的用户, 同时让 .lh5- 作为只读稳定兼容目标?见 下一节

zstd -1 是"最佳" zstd 级别吗?

简短回答:对,zstd -1 是 zstd 里速度最快的一档 (压缩率最低、速度最高)。但"最佳默认"是 -3, 不是 -1。

zstd 级别表

级别角色 压缩率 vs -3编码速度 vs -3
-1最快 ~5-10 % 差~2-3× 快
-3默认——速度/压缩率甜点 1.0×1.0×
-6平衡 ~5-8 % 好略慢
-10偏好压缩 ~10-15 % 好3-5× 慢
-15高压缩 ~20-25 % 好8-10× 慢
-22最大 ~30-50 % 好30-50× 慢

为什么 -3 才是默认

速度/压缩率曲线不是线性的:

所以 zstd 作者(Yann Collet)选 -3 作默认:它在曲线的 肩上。-1 大多数场景下压缩率损失太大;-10 花时间多但 收益少。

zstd -1 不是"全场最快解码器"

zstd -1 是最快zstd,但:

压缩器解压速度 有熵编码?
lz4~3-4 GB/s
snappy~3-5 GB/s
zstd -1~1.5-2 GB/s是(FSE / tANS)
zstd -3~1 GB/s
brotli -1~300-500 MB/s是(静态 Huffman)
deflate -1~500-800 MB/s是(动态 Huffman)
lha -lh5-(当前)~45 MB/s是(静态 Huffman)

lz4 / snappy 比 zstd -1 还快 1.5-2×,因为 它们根本没有熵编码器。所以"最快解码器"是 双轨赛跑;lz4 赢无熵轨;zstd -1 赢有熵轨。

LHA 相对 bzip2 / zip / 7z 的独有特性是什么?

简短回答:lha 的"特性"大多数不是特性—— 是换 35 年稳定格式的交换。但 lha 有几个真独有的: 每文件独立压缩、stdio 流友好、可流式构建目录。

LHA 没有

特性bzip2zip7zlha
块内随机访问(seek 到 100 KB 解 1 KB) ✅ 100-900 KB 块 ⚠️ 仅按文件跳 ✅ 块 ❌(流式)
全局索引 ✅ 中央目录在末尾 ❌(但可流式构建)
稀疏文件 / 硬链接 / ACL / xattr 部分
强校验和(SHA-256) ❌(CRC32) ❌(CRC32)
压缩字典 / pretrain

LHA 是流式(不是块)归档器——和 zip / gzip / tar 同一 形态,不是 bzip2 / 7z 那一档。所以 bzip2 的"seek 到 100 MB 处只解 1 KB"在 lha -lh5- 上结构上就不可能。

LHA 的(不寻常的)

战略意义

LHA 在 2026 年的价值是格式稳定性 + 零依赖 + 小体积 + 35 年可移植,不是"功能丰富"。要 bzip2 风格的功能 (随机访问、索引、强校验)就加新格式 .lh5b-(块)、.lh8-(tANS)、 .lhi-(索引)——不是 Rust 重写 LHA。详见 下个问题

Rust 重写能帮 LHA 吗?(lha-rs 问题)

简短回答不能。lha-rs 不值得作为 1:1 替换 C 版的 Rust 移植。诚实的答案:Rust 重写的标准理由对 lha 基本不适用。

每个标准"用 Rust 重写"的理由,应用到 lha

"理由"对 lha 的现实
内存安全(Rust 消除 strcpy/sprintf 风险) 5 维审计刚做完。8 个 strcpy 全部带长度检查; 0 个 sprintf / gets / scanf。脆弱类不存在。整体重写 来去掉一个不在的漏洞类别,成本/收益差。
cargo-fuzz / cargo test 基础设施 libFuzzer 不只是 Rust 用,给 C / C++ 项目也能挂。 oss-fuzz 给 C 项目免费 hosting。给 lha 加 libFuzzer 是 1-2 周工作,不是单独开项目。
现代错误处理(Result<T, E>) C 用 tagged-union 一样能返回。真正的活是错误词汇, 不是语言。1 周改 lha 的错误路径,不是 1 年。
现代 CLI(clap / argh) lha 的 CLI 1990 年就稳定。重构是破坏性变更,不是 改进。CLI 不是 lha 的痛点。
可插拔压缩 crate(flate2、zstd-rs、lz4_flex) 那是给新项目用的。不需要在 lha-rs 里——任何写新 Rust 工具的人已经在用了。
算法优化载体 层不对。预计算表 + memcpy 两招是语言无关的。 C 里写——进现有 CI 矩阵,不需要新项目。
新格式实验(tANS 等) 这是新格式的问题,不是 lha-rs 的问题。新格式 住在 lha/ 的 src/lh5b/src/lh8/。不是单独 repo。

什么时候 Rust 重写才真的对

必须满足以下之一:

今天对 lha 这三条都不命中。

正确的下一步

  1. 留在 lha(C)——维护和审计。5 维安全审计已做; libFuzzer 集成 1-2 周;oss-fuzz 1-2 小时。完成。
  2. 在 C 里加新格式(作为子目录)src/lh5b/(块格式带随机读)、 src/lh8/(tANS)、src/lhi/(索引 文件)。C 实现直接进现有 CI 矩阵;没新 repo、没新 release 节奏。
  3. 写格式规范docs/lzh-format.md, 任何未来实现(Rust、Python、Go)都有单一事实源。
  4. 如果新格式被证明有用,然后单独开 ljh-sh/lha-spec(或类似)放多种参考实现。 不是"重写" repo——是"多实现"测试床。

lha-rs 不在这个清单上。6-12 个月重写一个 13,400 行 C 项目,安全画像已经好,用一个不增加价值的语言——这 不划算。保留 C、做小的增量、必要时用 Rust 做新格式——这就是 正确路径。

新格式(.lh5b- / .lh8- / .lhi-)放哪里?

简短回答:在现有的 ljh-sh/lha 仓库里, 不是新仓库。新格式是对 lha 家族的补充,不是 替代。

提议的新格式

后缀后端 和 .lh5- 兼容 加了啥
.lh5-(当前)LZHUF + 逐字节复制 —(基准) 35 年稳定格式;体积小;零依赖
.lh5b-同 .lh5-,但 256 KB 块 老 .lh5- 读端能解;新读端获块内随机读 单文件内随机访问;部分解(接近 bzip2 行为)
.lh8-tANS(FSE)+ 更快的 LZ77 新格式,老 .lh5- 解码器读不了 解压速度和 deflate -1 同一档;多数语料上压缩率 优于 .lh5-
.lhi-索引文件(独立于归档) 和任何 .lh5-(或 .lh8-)归档配合 可流式构建的目录;让 lhacat archive.lzh 列出所有文件而 不读任何 body

每个放哪里

一个二进制处理所有这些

在现有 repo 做的杀手特性:单个 ljh-sh/lha 二进制读所有这些格式:

$ lha l archive.lh5-          # 基准
$ lha l archive.lh5b         # 块内随机读
$ lha l archive.lh8-          # tANS,更快解压
$ lha l --index archive.lhi   # 流式目录
$ lha x archive.lh8-          # 写新 tANS 归档

单静态二进制,~250 KB,零依赖。同一 x i lha / x eget use lha 安装路径。35 年稳定读路径 + 现代 tANS 写路径 + 块内 随机读。这是 lha 在 2026 年的竞争定位: 一个做没有任何其他归档器能做到的事情的二进制, 重量 250 KB。