比较编码和压缩之前,先把怎么测说清楚:用什么数据、怎么计时、压缩比怎么定义、哪些因素会让数字看起来不一样。这篇把后面所有篇目共用的方法和一些容易误会的地方先量一遍。
数据集 记录数 JSON Lines 字节 平均每条 不同的键名数 特点 products 20000 7214161 360 12 爬虫抓到的商品记录:字符串多、字段名每条都重复 timeseries 50000 2744541 54 4 数值密集:时间戳递增、数值随机游走 nested 3000 5582050 1860 8 嵌套 5 层、键名大量重复的配置式文档 blobby 3000 (含二进制) 5 每条带 512 字节随机二进制缩略图(共 1500 KiB,不可压缩) pages 1500 20859437 13906 类 HTML 页面文本,每页约 13 KiB,用于纯压缩实验
所有数据集都由固定随机种子的脚本生成:products(爬虫商品)、timeseries(数值)、nested(嵌套配置)、blobby(含随机二进制)、pages(类 HTML 文本)。它们是合成数据,真实数据的压缩比会和这里有差距,每篇的结论只对这些数据成立。
CPU 核数 10;第一次 38.5 ms;之后 9 次:最小 37.3 / 中位 38.2 / 最大 38.7 ms(最大是最小的 1.04 倍) 同时跑着 10 个空转进程:最小 37.2 / 中位 51.1 / 最大 71.6 ms(中位是无负载时的 1.34 倍,最大是最小的 1.92 倍)
计时用同一操作重复多次取中位数,并在机器空闲时测。实验里 json.dumps 在空闲时最大只是最小的 1.04 倍;同时开满 CPU 空转进程时,中位数变成 1.34 倍、最大值是最小值的 1.92 倍(三次运行里中位数的变化在 1.04 到 1.34 倍之间),所以不用单次或最大值。
基准 原始字节 zstd-3 后 原始/压缩后 压缩后/原始 省下 紧凑 JSON(无空格) 7214163 1331144 5.42× 18.5% 81.5% 默认 json.dumps(逗号冒号后带空格) 7754162 1347541 5.75× 17.4% 82.6% indent=2 的 JSON 9594164 1441638 6.66× 15.0% 85.0% JSON Lines(紧凑) 7214161 1331194 5.42× 18.5% 81.5% 压缩后的字节数:紧凑 1331144、默认 1347541、indent=2 1441638——相差不大,"压缩比"主要是被不同的基准拉开的
“压缩比”可以写成原始/压缩后(5.42×)、压缩后/原始(18.5%)或省下的比例(81.5%)。更要紧的是基准:同一个压缩结果,拿 indent=2 的 JSON 当基准比拿紧凑 JSON 高出一大截。后面各篇一律给出压缩后的字节数,基准明确写成“紧凑 JSON”。
每批 1 条:共 5771649 字节,压缩比 1.25×(原始 JSON 字节 7194162),平均每条 288.6 字节 每批 10 条:共 2472557 字节,压缩比 2.91×(原始 JSON 字节 7194162),平均每条 123.6 字节 每批 100 条:共 1746783 字节,压缩比 4.12×(原始 JSON 字节 7194162),平均每条 87.3 字节 每批 1000 条:共 1421724 字节,压缩比 5.06×(原始 JSON 字节 7194162),平均每条 71.1 字节 每批 20000 条:共 1331194 字节,压缩比 5.40×(原始 JSON 字节 7194162),平均每条 66.6 字节
压缩器只能利用它已经看到的内容。每条记录单独压缩时 2 万条只有 1.25×,整批压缩有 5.40×,每批 1000 条已经有 5.06×。小对象的压缩需要字典或攒批,见第 17 篇。
内容 gzip-6 zstd-3 lz4 (压缩比 = 1 MiB / 压缩后) 随机字节 randbytes 1.00× 1.00× 1.00× 随机小写字母 1.57× 1.68× 1.00× 词表里的词(4000 个词随机拼) 2.52× 2.81× 1.34× products 的 JSON Lines(截 1 MiB) 4.82× 5.29× 3.10× 重复的 1 KiB 块 166.60× 927.12× 195.52× 全零 1009.22× 20971.52× 241.61×
随机字节怎么压都是 1.00×;随机小写字母约 1.6×;词表里的词拼接约 2.8×;有结构的 JSON Lines 约 5.3×;重复的 1 KiB 块能到 927×。后面的“高压缩比是怎么做到的”几篇会逐个解释这些数字的来源。
单线程 zstd-3 压缩 5 次:不同的输出有 1 种(['5f5df667a708'])
threads=0/2/4 的输出:{0: (1331194, '5f5df667a708'), 2: (1331194, '5f5df667a708'), 4: (1331194, '5f5df667a708')};字节完全相同
压缩 6.9 MiB 的中位耗时:threads=0 14.7 ms、threads=2 15.8 ms、threads=4 15.0 ms
换成 19.9 MiB 的 pages 数据:threads=0 4151340 字节(54 ms)、threads=4 4147915 字节(26 ms);字节不同同一个库、同一份数据、同一个级别,压缩输出是确定的。多线程压缩大数据(19.9 MiB)时输出会和单线程不同,体积也略有差别,需要字节级可复现(例如对压缩后的文件做校验和)时要注意。