
同一份 19.4 MiB 的英文词表,交给四个压缩器,十个档位各压一遍再解一遍。体积最小的一档是 xz -6,6.8%;解压这一列大多在 0.1 秒以内,慢的那个是 bzip2。数字并排之后能看清另一件事:档位往上加,换来的收益越来越薄。
语料与四个压缩器的版本
== 0. 运行时与压缩器版本 ==
平台: Darwin arm64 (内核 25.2.0)
CPU 核心: 10
语料: /usr/share/dict 的 propernames + web2 + web2a 各拼 4 轮,每轮行首加轮次编号
语料字节: 20330428 (19.4 MiB) / 行数: 1253956
语料 sha256: 24f67041163be7375b8344a5af72376f7db702d0b3d8b4764fb6e391dccc6490
gzip: Apple gzip 475
bzip2: bzip2, a block-sorting file compressor. Version 1.0.8, 13-Jul-2019. | Copyright (C) 1996-2019 by Julian Seward.
xz: xz (XZ Utils) 5.8.3 | liblzma 5.8.3
zstd: *** Zstandard CLI (64-bit) v1.5.7, by Yann Collet ***
语料是脚本当场拼的:/usr/share/dict 里的 propernames、web2、web2a 三份词表各拼 4 轮,每轮给行首加一个轮次编号。加编号是为了不让整段重复被 xz -9 那种大字典一次性匹配掉,那样出来的体积低得不实在。拼完 19.4 MiB,一百二十五万行,四个压缩器都从 stdin 读、往 stdout 写,解压后再和原语料逐字节比对 sha256。
十档并排,体积和耗时一起看
== 1. 十档组合,同一份语料,压缩与解压各取 3 次中位数 ==
命令 压缩后字节 压缩率 压缩s 解压s 解压校验
xz -6 1390868 6.8% 3.629 0.093 OK
xz -9 1391308 6.8% 4.020 0.095 OK
zstd -19 1463833 7.2% 4.420 0.024 OK
xz -1 4469496 22.0% 0.142 0.048 OK
bzip2 -1 4530276 22.3% 0.638 0.224 OK
gzip -9 4620568 22.7% 1.840 0.023 OK
gzip -6 4623306 22.7% 0.344 0.023 OK
bzip2 -9 4736574 23.3% 0.649 0.242 OK
zstd -1 5625015 27.7% 0.070 0.029 OK
gzip -1 5694097 28.0% 0.096 0.022 OK
十档解压结果全部与原语料逐字节一致: True
按体积排,前三名是 xz -6、xz -9、zstd -19,都落在 7% 上下,和第四名 xz -1 的 22.0% 之间隔着一大段。xz 的两个高档差得很小,1390868 对 1391308 字节,只隔 440 字节,压缩耗时却多出 0.4 秒。
这一列里最扎眼的是 bzip2。它在词表这种重复度高的文本上没占到便宜,-9 压到 23.3%,比 gzip 的 22.7% 还大,比自己的 -1 也大。块排序平时的口碑在这一份数据上没兑现。
gzip 两档之间更是看不出区别:-6 是 4623306 字节,-9 是 4620568 字节,只少 2738 字节,压缩耗时从 0.344 秒涨到 1.840 秒,五点四倍。多压的那点字节,是用秒换来的。
解压那一列,bzip2 是唯一掉队的
== 3. 压缩与解压的耗时对比(第二、五列,越小越快) ==
zstd -1 压缩 0.070s 解压 0.029s 解压/压缩 0.41x
gzip -1 压缩 0.096s 解压 0.022s 解压/压缩 0.23x
xz -1 压缩 0.142s 解压 0.048s 解压/压缩 0.34x
gzip -6 压缩 0.344s 解压 0.023s 解压/压缩 0.07x
bzip2 -1 压缩 0.638s 解压 0.224s 解压/压缩 0.35x
bzip2 -9 压缩 0.649s 解压 0.242s 解压/压缩 0.37x
gzip -9 压缩 1.840s 解压 0.023s 解压/压缩 0.01x
xz -6 压缩 3.629s 解压 0.093s 解压/压缩 0.03x
xz -9 压缩 4.020s 解压 0.095s 解压/压缩 0.02x
zstd -19 压缩 4.420s 解压 0.024s 解压/压缩 0.01x
解压耗时和压缩比并不挂钩。bzip2 两档解压都要 0.22 秒以上,是 gzip 的十倍,也是全表最慢。压缩时那套块排序要在解压侧反着再做一遍,这笔账绕不开。
剩下的三家里,gzip 解压 0.022 秒,zstd 在 0.024 到 0.029 秒之间,xz 慢一些,0.093 到 0.095 秒。xz 压得最小,解压却比 zstd -19 慢将近四倍。压缩比最高的几档解压并不慢:zstd -19 的 0.024 秒,和 gzip 最快的 0.022 秒贴着。压得狠就解得慢这句判断,只在 bzip2 身上成立。
十核没帮上忙
== 2. 多线程开关对结果的影响(同等级,加 -T0) ==
zstd -19 体积 1463833 bytes / 4.404s
zstd -19 -T0 体积 1463833 bytes / 4.308s
体积是否改变: False / 耗时比 1.02x
xz -6 体积 1390868 bytes / 3.568s
xz -6 -T0 体积 1390868 bytes / 3.568s
体积是否改变: False / 耗时比 1.00x
这台机器十核,给 zstd -19 和 xz -6 加上 -T0,体积一个字节没动,耗时也基本原地踏步,比值 1.02 倍和 1.00 倍。多线程切不开多半是因为数据量不够:xz 的分块尺寸跟着字典大小走,高档位的字典已经大过整份语料,只有一块可压;zstd 在高档位的窗口也接近输入体积。要等输入大到能真正分块,线程数才用得上,19.4 MiB 这个量级看不到差别。
落到具体档位
定期归档、之后还要解压回来的数据,zstd -19 和 xz -6 体积都在 7% 上下,但 zstd 解压快将近四倍,长期要反复读的那一类放它更合适。只压一次、几乎不再解开的,xz -9 的 6.8% 是全场最小,多花的那 4 秒付得起。
管道中间环节、临时传一下,gzip 的 -6 就够,别升到 -9,那多出来的 1.5 秒只换到 2738 字节。手上只有 .gz 这一种格式认的地方,也没有更好的选项。
bzip2 在这份词表上没有站得住脚的理由:压不过 gzip,解压又是最慢的一档。我不建议在新脚本里默认调它,除非手里的数据正好是它擅长的另一种形态。这条只覆盖词表这一类文本,换成二进制或者已经压过的数据,几个工具的名次可能会换位。