算法 — 历史、性能、当下定位

lha 携带的是 LZHUF:1990 年代的 LZ77 + 动态 Huffman,为「1 MiB 内存的 286 上的快速 解压」而设计,写在 SIMD 还没出现的年代。这一页说的是这 个算法、在 2026 年它付出的代价,以及为什么一个外部 项目的单一静态二进制仍然会以它为底座。

算法 · 历史 · 性能 · 当下 · 总结

1. 算法

lha 家族里的每一种压缩方式(-lh0--lh7-,外加 -lhd- 目录头部)都是 同一个两步结构:

  1. LZ77 风格的字符串匹配。在输入里查找重复的字节 序列,并发出形如 (distance, length) 的回 引用,代替重复字节。所有 LZARI / LZHUF / ZIP / gzip / zstd / lz4 / 7-zip 里的「LZ」都是这一个想法,源头是 Lempel & Ziv 1977 年的论文。
  2. 熵编码器,作用在字面量和回引用上。这是各家不同 的部分:
    • -lh1-LZARI: LZ77 + 算术编码(Okumura,1988)。
    • -lh5-LZHUF: LZ77 + 动态 Huffman(Tagawa,1990)。默认方式。
    • -lh4--lh6--lh7--lzs- — 熵后端的实验性改进,或为兼容而保留的未使用编码。

静态 vs 动态 Huffman

LZHUF 用的是静态 Huffman 表,每个块重算一次,输入 里每几 KB 重新派生一次。这个设计抉择决定了整套取舍:

位串行解码循环(以及为什么它慢)

LZHUF 的解码循环是逐比特读入比特流,每次通过 一个小型 2× 表查找下一个符号。这从根本上就是串行的: 比特 n+1 必须在比特 n 查表完成后才能读。

ANSI C 源码就是直白的 1995-2003 年代码,没有 SIMD intrinsic:

$ grep -nEi '(mmx|sse|avx|simd|__m128i|__builtin_ia32)' upstream/lha/src/*.c
$                       # 零命中

把 LZ77 的 length-distance 拷贝向量化,可以拿到大约 1.5–2× 的解压速度;位串行的 Huffman 循环就是天花板。 要在解压上再快,必须换算法,不能再微优化 LZHUF(更长 的版本见 基准页)。

2. 历史 — 世系

首页的历史章节对 1988-2022 这一段弧线讲得很详细;紧凑版:

3. 性能 — 三条轴

基准数据来自 1.83 MiB 的 kenlm 源 码语料,每个 cell 跑 5 次,取中位数 ± 样本标准差。要点:

工具级别压缩 解压压缩率
lha-lh5-(默认) ~50 ms~40 ms 29.93 %
zip-6(默认) ~62 ms~3 ms 31.01 %
tar(不压缩) ~80 ms~82 ms 149.55 %
tar.gz-6(默认) ~210 ms~5 ms 24.93 %

三条值得记的观察

  1. 压缩速度。lha 排在 zip(最快,领先 ~22 %)和 tar.gz(最慢,慢 ~4×)之间。对于「压缩后 留存」类负载(构建产物留存、镜像站),lha 是非平凡压缩器里最便宜的,仍然比裸流传得快 ~3×。
  2. 解压速度。ziptar.gz 解压在个位数毫秒;lha 要 ~40 ms。位串行 Huffman 循环就是天花板。绝对时间仍然很小 — 远 低于任何 CDN 的网络 RTT 噪声。在 CDN 上从镜像重新 解压 100 次的工作负载,被 HTTP 延迟支配,而不是算法 选择。
  3. 压缩率。lhadeflate 高 ~5 个百分点(1.8 MiB 源码上多 ~91 KB)。这是 1990 年的有意取舍:调优目标是 286 上解压快,而不是 2020 年 工作站上压得最紧。

哪里输给现代算法 — 哪里不输

基准页的「深入」章节讲算法 内部原因。短版本:lha 是 1990 年的设计,到今天没变过, 速度天花板就是算法本身,不是实现。

4. 当下定位 — 为什么这个项目在 2026 年还存在

被 vendored 的 jca02266/lha@86094cb 源码,是 1995-2003 年代的 ANSI C 代码库,2022 年只经历过一次现代 化(autotools)。ljh-sh/lha 从那里出发只做一件事:为 每个平台产出一个 lha 的单一静态二进制,让 用户不需要编译器

2026 年这个命题有三类具体需求场景:

4.1 老 .lzh 的解压

最直观的情况:某人手上有 1994 年的游戏补丁、2ch 风格的 BBS 历史附件、或 2000 年代的日本开源归档。现代工具都打不 开 .lzh。装上 x eget use lha, 两秒钟就到 ~/.local/bin,再 lha x foo.lzh 就能解。少了这个二进制,用户 只能装编译器、找 1990 年代的 autoconf 补丁、然后祈祷 他们的 libc 还没把这构建搞坏。

4.2 单文件静态二进制用于 CI 产物 / CDN 分发

lha 从任何源码树产出一个 .lzh 归档,耗时 ~50–80 ms,主机上零系统依赖。对于 CI 产 物发布者(任何把构建结果打包下载的人来说),意思是:

这就是 ljh-sh 在各项目里都采用的「静态二进制分发约定」 (kenlm、upxz、现在的 lha)。具体流水线可以看 构建审计

4.3 x-cmd 的 zuz 模块 — 直接动机

这是本项目以静态构建形式分发的根本理由。

x-cmdzuz 模块对归档文件做格式自动识别:按扩展名 选工具,透明地解压或压缩。它支持 tartar.gztar.bz2tar.xzzip7zrarlha 等。但它有一条架构铁律:

绝不内嵌格式专用的工具。始终调用系统已安装的 二进制。

问题落在 x-cmd issue #131(2024 年 12 月):某个用户希望 zuz 支持 LHA/LZH,但 x-cmd 当前支持的所有便携式包管理器都不 自带 lha。讨论线索如下:

也就是说,zuz 用户的主安装路径是:

x uz foo.lha
# zuz 检测到 .lha,发现 PATH 里没 lha,
# 回退到:eget ljh-sh/lha --to ~/.local/bin/lha
# 然后:~/.local/bin/lha x foo.lha

不是 Homebrew。不是 apt。不是各发行版的包。一个默认 的静态安装,不需要特权安装、不需要 tap、不需要构建 链、在 x-cmd 支持的每个平台上都能用。本仓库存在的全部 理由,就是为它提供这一份「默认静态安装能力」。

如果你是 x-cmd 用户,在一台没装 lha 的系统 上 x uz foo.lha,你就是在用这个二进制。 如果你从来没用过 x-cmd,只是要打开一个 .lzh,你也在用这个二进制 — 同一份产物、 同一个发布、同一个被 vendored 的源码。

4.4 这个项目「不是」什么

老实讲清楚边界:

5. 总结

想要一条经验法则:

如果你关心…就选
打开一个 1994 年的 .lzh lha(本项目的二进制)
闭盒系统上的单一静态二进制 lha(~150 KB,零依赖)
类似 zuz 的格式自动识别 lha(惰性从 ljh-sh/lha 拉)
长尾 CDN 上最小的产物 tar.gz -9zstd
微秒级解压 lz4zstd

这一页存在的理由是:「这个二进制是什么」和「为什么它 存在」值得集中在一个地方,而不是被历史基准构建审 计FAQ 各页瓜分。