bigywater.tech
数据编码与压缩 · 19

编码加压缩的组合矩阵:体积与端到端耗时

编码和压缩是两件事,实际是叠加使用的。把 JSON、MessagePack、Protobuf 与 snappy、lz4、gzip、zstd、brotli 各种组合,在三个数据集上量体积和耗时,看看到底哪种组合在什么情况下最合适。

14.1%
products 上最小的组合(JSON + brotli q11 = 1015149 字节)占未压缩 JSON 的比例
+3.5%
zstd-3 压缩后 Protobuf 比 JSON 大的比例(1378051 对 1331144);未压缩时小 32%
9–16 对 45–50 ms
解压加解码 2 万条:Protobuf 与标准库 JSON

体积

实测 · 本机执行输出
  --- products 2 万条 ---
  编码                   不压缩      snappy         lz4      gzip-6      zstd-3     zstd-19   brotli q5  brotli q11
  JSON             7214163     2511955     2314042     1484671     1331144     1047909     1216613     1015149
  MessagePack      6357308     2458222     2385707     1496373     1413136     1077896     1268320     1061068
  Protobuf         4914350     2295420     2298068     1449113     1378051     1081117     1239033     1054849
  最小的组合:JSON + brotli q11 = 1015149 字节(JSON 不压缩的 7214163 字节的 14.1%);JSON + zstd-3 = 1331144;Protobuf 不压缩 = 4914350

未压缩时 Protobuf 比 JSON 小 32%(products)、65%(timeseries)、76%(nested)。但压缩之后差距大幅缩小:products 上 zstd-3 的 Protobuf 反而比 JSON 大 3.5%。最小的组合都是 JSON 加高级别压缩(brotli q11 或 zstd-19)。

耗时

实测 · 本机执行输出
  编码耗时:JSON(orjson) 5.7 ms、MessagePack 13.5 ms、Protobuf(从 dict 构造) 34.9 ms
  编码+压缩                snappy        lz4     gzip-6     zstd-3    zstd-19  brotli q5 brotli q11   |     snappy      lz4   gzip-6   zstd-3  zstd-19brotli q5brotli q11  ← 右边是 解压+解码(并完整访问 title 字段)
  JSON                   14.6       16.4       97.3       19.5     1960.6       75.1     8501.7   |       49.6     45.4     48.7     47.0     46.3     49.0     50.8
  MessagePack            22.6       24.8      126.3       29.4     1849.6       87.6     7503.2   |       37.2     34.8     37.9     36.9     33.2     37.8     38.3
  Protobuf               42.6       44.1      131.9       49.6     1382.6      111.9     5623.5   |       10.7      9.1     11.9     11.4     10.5     15.9     14.4
  (左边 = 编码 + 压缩 毫秒,JSON 用 orjson 编码;右边 = 解压 + 解码毫秒,JSON 用标准库 json.loads)

压缩级别的影响远大于编码格式:zstd-3 对 zstd-19 差约 100 倍,brotli q11 要 8 秒。编码本身,orjson 的 JSON 最快(5.7 ms),从 dict 构造 Protobuf 消息最慢(34.9 ms);解码 Protobuf 最快(9–16 ms)。

前沿

实测 · 本机执行输出
  前沿:JSON + brotli q11            1015149 字节    8319.6 ms
  前沿:JSON + zstd-19               1047909 字节    1843.6 ms
  前沿:MessagePack + zstd-19        1077896 字节    1474.2 ms
  前沿:Protobuf + zstd-19           1081117 字节    1241.7 ms
  前沿:JSON + brotli q5             1216613 字节      79.4 ms
  前沿:JSON + zstd-3                1331144 字节      21.0 ms
  前沿:JSON + lz4                   2314042 字节      16.9 ms
  前沿:JSON + snappy                2511955 字节      14.8 ms
  (不在前沿上的组合,同样大或更大的体积至少有另一个组合比它更快;共 21 个组合,前沿上 8 个)

体积和耗时的前沿上,6 个点是 JSON(orjson 编码极快),只有 zstd-19 一档有 MessagePack 和 Protobuf。要的是小体积还是低延迟,先确定压缩档位,再看格式。

数值密集的数据

实测 · 本机执行输出
  --- timeseries 5 万行 ---
  编码                   不压缩      snappy         lz4      gzip-6      zstd-3     zstd-19   brotli q5  brotli q11
  JSON             2744543      596771      649008      313607      352531      174010      276226      168309
  MessagePack      2000003      569266      580243      321012      298689      246901      268467      218938
  Protobuf          939568      554195      570575      304961      343932      235390      245860      230233
  最小的组合:JSON + brotli q11 = 168309 字节(JSON 不压缩的 2744543 字节的 6.1%);JSON + zstd-3 = 352531;Protobuf 不压缩 = 939568

timeseries 的 Protobuf 未压缩比 JSON 小 65%,但 zstd-19 压缩后 JSON(174010)比 Protobuf(235390)小。数值有规律时文本的规律被压缩器吃得很干净。

嵌套结构

实测 · 本机执行输出
  --- nested 3000 个文档 ---
  编码                   不压缩      snappy         lz4      gzip-6      zstd-3     zstd-19   brotli q5  brotli q11
  JSON             5582052     1069231     1117578      553565      660737      339808      496377      340290
  MessagePack      4062921      977264     1047168      546978      606310      340997      497111      350517
  Protobuf         1366787      746981      679347      431831      440781      361178      405303      358818
  最小的组合:JSON + zstd-19 = 339808 字节(JSON 不压缩的 5582052 字节的 6.1%);JSON + zstd-3 = 660737;Protobuf 不压缩 = 1366787

nested 上 Protobuf 在 zstd-3 下比 JSON 小 33%,但 zstd-19 下 JSON(339808)反超 Protobuf(361178)。压缩级别越高,差距越小。

逐条

实测 · 本机执行输出
  编码                不压缩   snappy      lz4   gzip-6   zstd-3   zstd-3+字典   (平均每条字节)
  JSON            359.6    355.1    371.1    274.3    288.6       109.1
  MessagePack     317.8    322.6    340.3    289.3    298.9       112.5
  Protobuf        242.6    246.3    264.0    231.2    232.7       106.5

每条记录独立处理(不能攒批)时,Protobuf 不压缩就比 JSON 小 33%,但加上 zstd 字典后三种编码只差 6%(106.5、109.1、112.5)。snappy 和 lz4 在单条上几乎没用。

分步演示

编码加压缩
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8(系统 Python,自带 compression.zstd)/ zstandard 0.25.0 / lz4 4.4.5 / zstd、lz4、brotli、protoc 命令行(Homebrew);msgpack、protobuf、snappy 等第三方库临时装在临时目录里,用完删除;所有数据都是本机脚本生成的合成数据(固定随机种子),没有使用任何真实网站或用户的数据;计时用 time.perf_counter,取多次中位数,数字每次运行会有小幅抖动。orjson 3.13.0、msgpack 1.2.3、protobuf 7.36.2、python-snappy 0.7.3、lz4 4.4.5、brotli 1.2.0、zstandard 0.25.0;每个数据集整批编码后再压缩;耗时是 5 次(zstd-19 和 brotli q11 是 3 次)中位数;Protobuf 的编码耗时是从 Python 字典构造消息再序列化,包含了构造对象的开销;JSON 解码用标准库 json.loads,Protobuf 解码是解析后访问 title 字段;字节数是确定性的
    • 没有验证:其它 Protobuf 实现(Go、Java、C++)的编码速度;更大的数据量;把数据模型一起优化(价格存整数分等,见下一篇);列式布局(第 8 篇)与这些组合的叠加;多线程压缩;zstd-19 与 brotli q11 之间的差距是否随数据变化
    京ICP备2026067502号-1京公网安备11010502063487号