性能对比
在 1.83 MiB 的代表性语料上对 lha、
tar、tar.gz、zip 做压缩与解压对比,
每个工具都跑了默认和最佳两级。数字清晰地说明了每个工具
在哪一项上领先。
每格 5 次取中位数,附样本标准差。机器:Apple M 系列, 原生 lha 编译产物(LHa for UNIX 1.14i-ac20220213,默认 configure 选项)。Wall-clock 端到端:每格的 compress + decompress 总时间。
一眼看完的表
同一 1.83 MiB 语料,压缩解压各 5 次,取中位数。标准差反映 笔记本本身逐次运行的噪声。
| 工具 | 级别 | compress | decompress | size B | 压缩率 | ||
|---|---|---|---|---|---|---|---|
| 中位 | ±σ | 中位 | ±σ | ||||
| lha | -lh5- (默认) | ~50 ms | ±5 | ~40 ms | ±1 | 547,045 | 29.93 % |
| lha | -lh7- | ~46 ms | ±1 | ~40 ms | ±6 | 547,045 | 29.93 % |
| tar | 无压缩 | ~80 ms | ±4 | ~82 ms | ±5 | 2,732,544 | 149.55 % |
| tar.gz | -6 (默认) | ~210 ms | ±5 | ~5 ms | ±0.1 | 455,435 | 24.93 % |
| tar.gz | -9 (最佳) | ~210 ms | ±1 | ~5 ms | ±0.1 | 455,435 | 24.93 % |
| gzip -c | -6 (原始流) | ~170 ms | ±4 | ~5 ms | ±0.2 | 455,486 | 24.93 % |
| gzip -c | -9 (最佳) | ~170 ms | ±3 | ~5 ms | ±0.2 | 455,486 | 24.93 % |
| zip | -6 (默认) | ~62 ms | ±1 | ~3 ms | ±0.4 | 566,683 | 31.01 % |
| zip | -9 (最佳) | ~85 ms | ±1 | ~3 ms | ±0.1 | 566,683 | 31.01 % |
所有时间均为 wall-clock 端到端。"级别"是各工具自带的
压缩档位。语料文件数 304,未压缩总字节 1,827,165。
tar (无压缩)的压缩率超过 100% 是因为 tar
存储每个文件的元数据(UID、GID、mode 位、512 字节
每记录 padding);对零碎小文件来说,没经过 deflate 之前
就已经多了 50%。
怎么读这张表
1. compress 速度:zip < lha < gzip < tar < tar.gz
在默认级别下:
zip -6是压缩最快的(约 62 ms)— deflate、不带 tar 阻塞、每个文件少量开销。lha -lh5-排第二(约 50 ms),比 zip 慢 22 % 左右。gzip -6 -c处理原始 tar 流需要 约 170 ms —— deflate 即使在默认级别上也是比 LZHUF 复杂得多的编码器。tar -czf在底层 deflate 之上 又多出 ~40 ms 的 framing — 每条记录、 512 字节对齐的 padding。tar -cf(无压缩)约 80 ms — 只有 header 开销,没有 deflate。
在默认级别上,zip比 lha快 22%,
lha比 tar.gz快 76%。
compress 速度的相对排序在不同语料上稳定。
2. decompress 速度:tar.gz ≈ zip < lha < tar
解压方面速度排序很有趣:
zip和tar.gz解压在 3-5 ms — deflate 解码成本低。lha解压约 40 ms — LZHUF 的解码 是位串行的:每个 bit 都依赖前一个 bit 的表查找。tar解压约 82 ms — 同样的 512 字节记录 padding 开销在出口侧。
在 CDN 上解压 100 次的真实场景里,绝对解压时间被 网络延迟主导。三种工具的解压时间都在个位数毫秒级; 差异远低于磁盘寻道 + HTTP 请求的噪声底限。
3. 压缩率:tar.gz < gzip ≈ lha < zip
同一个 kenlm 语料上:
tar.gz -6和gzip -6 -c都达到 455 KB / 24.93% — deflate 在本语料上的 下界。-9没找到更多优化。lha -lh5-约 547 KB / 29.93% — 比 deflate 大约 20%,换来 ~2.2 倍的压缩速度。这是 LZHUF 在 1990 年设计时就做出的取舍:针对 "286 + 1 MB RAM 上快速解压",而不是体积最优。zip -6约 567 KB / 31.01% — 因为 zip 的 deflate 配置较旧,DEFLATE64(宽窗口变体) 默认关闭。tar (无压缩)2.7 MB / 150% — 记录 header + 512 字节 padding 让小文件膨胀一倍多。
tar.gz 和 lha 的差距约 5 个百分点
— 在 1.8 MiB 上就是约 91 KB。在快速 CDN 上重传
这 91 KB 不到 100 ms;在慢速 CDN 上要 1-2 秒。这就是
实际网线上的 size-vs-speed 权衡。
4. -9 在本语料上是个坑
对于已经具有相当冗余性的文本(源码里有大量重复的 关键字和空白),gzip -9 和 tar.gz -9 都比 -6 找不出 任何新的优化。语料丰富度不够让更高级别发挥作用;-9 在 compress 时间上贵了 5-10 倍,但压缩率提升 ≈ 0%。
如果你有真正高熵的内容(视频片段、数据库 dump、加密数据), -9 会拉开有意义的差距。本语料不满足这个条件。
什么场景用什么工具
| 如果关心的是… | 选 | 原因 |
|---|---|---|
| 最小可能的输出 | tar.gz -9 / zstd / brotli |
默认 level 9(或 zstd -19 / brotli -11)是从单文件
工具中能得到的最优压缩率。代价是 compress 时间
比 lha 慢 5 倍多,压缩率只差 5%。 |
| 最快的 compress | zip -6 |
约 62 ms。DEFLATE64 关闭,没有 tar blocking, 每个文件少量开销。比 deflate -6 损失 6% 压缩率。 |
| 最快的 decompress(CDN、OTA、镜像) | lha 或 zip |
两种工具在本语料上都在个位数毫秒级。lha 产物略大, 但解压后的代码体积本来就小;网络延迟主导。如果还关心 "无 zlib、无 libiconv 依赖",lha 才是答案。 |
| CI 产物 + CDN 的单文件静态二进制 | lha |
x eget use lha 把一个 152 KB
全静态二进制装到 ~/.local/bin,零依赖。
tar.gz需要 libz + libiconv + tar;
zip需要 libzip + libz。lha 没这棵树。 |
| Windows / macOS 用户开箱即用的分发 | zip(或 .tar.gz) |
zip 在 Windows + macOS 上是系统组件;tar.gz 系统直接
打开。lha 需要先装二进制(x eget use lha
一行解决,但仍然是个步骤)。 |
| 嵌入式 / 固件 / 老硬件 | lha |
在受限目标上解压器 < 16 KB。设计目标是 640 KB RAM 的 MS-DOS 机器。 |
深入阅读
为什么 lha -lh5- compress 比 tar.gz -6 快
LZHUF(.lh5- 背后的算法)使用 1990 年设计的
静态 Huffman 表 + 快速字符串匹配。deflate
(gzip / zlib)使用 LZ77 + 动态 Huffman + 滑窗,编码器
需要在滑窗里做选择。对没填满滑窗的输入(我们的 1.8 MiB
语料放 32 KB 窗口里还有富余),deflate 大部分时间
都在做匹配扫描;LZHUF 不需要。解压侧两者
几乎打平 — 解码回路结构差不多。
在比 deflate 窗口大得多的语料上(32 KB @ -6),deflate
会因为能找更远距离的匹配而拉开压缩率优势。我们的 1.8 MiB
语料太小,-6 已经接近局部最优,compress 时间上的差距
反而更显眼。要是对 100 MiB 语料,tar.gz -9
大约会以比 lha -lh5- 多 3-5 倍的 compress 时间
拉开压缩率。
为什么 tar (无压缩) 比 input 大那么多
tar 记录每个文件的元数据(UID、GID、mode、时间戳、文件名) 加上 512 字节/记录 padding。我们 1.8 MiB / 304 个小文件 的语料里,元数据占 50% 多。tar -czf 通过 LZ77 后端能 把大部分压回去,但 原始tar 对小文件语料就是 比 input 大。
为什么 zip -6 decompress 比 tar.gz -6 快
zip 的归档格式在文件末尾有一个 central directory —
解码器可以非流式地读它。tar.gz 必须对每条记录
扫 tar header 来找 size,然后让 deflate 解码器填
那个 exact size。zip 的 deflate 可以 wrap 整个 central directory;
tar.gz 的 deflate 是 per-record 的,每条记录要单独解析
header。差距在文件数变多时拉大。
为什么 lha 的 decompress 比 deflate / zstd / lz4 慢?
Decompress 是 LZHUF(.lh5-、.lh7-
背后的算法)显出年代感的地方。1990 年的设计是
位串行(bit-serial)的:
- 静态 Huffman 表,逐 bit 解码。位流按位读,
每 bit 决定下一次表查找。这是
src/huf.c里的解码主循环。此循环里没有并行性:bit n+1 必须在 bit n 查表之后才能读。 - LZ77 back-reference 串行解析。解码器读到一个
length/distance 对时,从 distance 之前
复制 length 个字节。
src/slide.c中的滑窗按字节更新,没有 SIMD memmove。在 1.83 MiB 语料上,这个 copy 循环主导了运行时间。 - 源码中完全没有 SIMD。
grep -nE '(mmx|sse|avx|simd|__m128i|__builtin_ia32)' src/*.c返回零命中。vendored 1.14i 代码是 1995-2003 年 的纯 ANSI C,没有架构特定的向量化。
所以这个问题的答案是:算法本身就是天花板,不是 实现。LZHUF 1990 年针对"286 + 1 MB RAM 上快速解压" 做调优,位串行解码回路就是天然契合。给 lha 加 SIMD 优化也许能在 decompress 上拿到 1.5-2 倍(length-distance copy 是唯一能向量化的大块),但底层的位串行 Huffman 循环 是根本串行的。要想解压更快,必须换算法,而不是对 LZHUF 做微观优化。
对比:zstd decompress 比 lha 在同等压缩率下
快约 5-10 倍,lz4 还能再快 3-5 倍。这两个
都是按现代 SIMD CPU(SSE2/AVX2 on x86_64,NEON on
aarch64)调优的,decompress 回路是 SIMD 友好的。lha
比 MMX(1996)早六年,最初的维护者没有向量单元可打。
什么时候这有影响:lha 是 "系统上没装 zstd/lz4 时 一个单文件静态二进制"的正解。lha 不是需要 微秒级 decompress 的高吞吐 CDN 的正解。
怎么复现
在任何 Unix-like 主机上(macOS、Linux、BSD):
git clone --depth 1 https://github.com/ljh-sh/lha.git
cd lha
bash scripts/build.sh
cp build/src/lha /usr/local/bin/
# 语料
test -d corpus/kenlm_src || cp -R ../kenlm/upstream/kenlm corpus/kenlm_src
# 每工具 5 次取中位数。表中数字来自 5 次(median ± sample-stdev),
# Apple M 系列笔记本。
for tool in \
'lha :lha c k.lh5- corpus/kenlm_src' \
'tar :tar -cf k.tar -C corpus kenlm_src' \
'tgz6 :tar -czf k6.tgz -C corpus kenlm_src' \
'tgz9 :tar -czf -9 k9.tgz -C corpus kenlm_src' \
'zip6 :zip -qr -6 k6.zip kenlm_src' \
'zip9 :zip -qr -9 k9.zip kenlm_src' ; do
label=${tool%%:*}; cmd=${tool#*:}
rm -f k.*
for r in 1 2 3 4 5; do
rm -rf corpus/kenlm_src; cp -R ../kenlm corpus/kenlm_src
t=$( { time $cmd >/dev/null 2>&1; } 2>&1 | awk '/real/{print $NF}')
sz=$(stat -c%s k.* 2>/dev/null | head -1)
echo "$label t=$t sz=$sz"
done
done
数字会因机器而异(不同 CPU 之间差约 10%,跨 macOS / Linux 差约
5%),但 compress 速度的相对排序
(zip → lha → tar → tar.gz)、decompress
(tar.gz → zip → lha → tar)、压缩率
(tar.gz → lha → zip → tar) 是稳定的。