侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 166 篇文章
  • 累计收到 7 条评论

xz -1 比 gzip -9 又快又小,档位高不一定划算

2026-10-8 / 0 评论 / 7 阅读

xz -1 比 gzip -9 又快又小,档位高不一定划算

同一份 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,解压又是最慢的一档。我不建议在新脚本里默认调它,除非手里的数据正好是它擅长的另一种形态。这条只覆盖词表这一类文本,换成二进制或者已经压过的数据,几个工具的名次可能会换位。

    🤞 分享