2201 字
11 分钟

SFC 性能报告:v2.1.0 / v2.2.0 / v2.2.1

项目仓库: Gitee | GitHub

本文所有数字都用吞吐量表示,即每秒处理多少数据,单位 MiB/s,越大越好。不用耗时秒数,因为改数据量、改文件数都会让秒数失去可比性,吞吐量才是能横向对照的刻度。涉及小文件时另用文件/秒,因为此时瓶颈在文件个数而不在数据量。

1. 性能是由什么决定的#

压缩一个目录,时间花在三件事上:

  1. 从硬盘把文件读进来
  2. 压缩、加密
  3. 把结果写到硬盘

程序里这三件事由不同的线程分别负责,可以同时进行。所以整体快慢不是三者相加,而是由最慢的那一件决定。

这里有个关键区别:第 2 件可以拆给多个核心同时做;第 1 件和第 3 件在程序里各自只有一个线程,加再多核心也快不了。由此可以直接推出本文最重要的结论:如果第 2 件已经比第 1、3 件快了,那么再优化压缩算法也不会让程序变快。当前就是这种情况,证据见第 3 节。

2. 测量条件#

硬件为 AMD Ryzen 7 8845H,8 物理核 16 逻辑处理器,内存 23.3 GB。磁盘 8 GiB 顺序写 2880 MiB/s,顺序读 6775 MiB/s。

测试数据三组:

代号内容规模特性
NCIEP真实工程树,45,390 个文件13.47 GiB近不可压缩,归档 13,430 MiB
cpu_corpus16 个文件2,048 MiB高可压缩,归档 896 MiB,往返全都落在系统内存里
fewbig16 个大文件13,312 MiB随机不可压缩,规模与 NCIEP 相当,但没有逐文件开销

版本基准:v2.1.0 取标签 v2.1.0-windows-GUI,v2.2.0 取 2.2.0 代码线的末端 09144c1,v2.2.1 取 a4c44e5。三个版本按各自的发布编译选项构建,v2.1.0 与 v2.2.0 为 -O3 -DNDEBUG,v2.2.1 另外开启 LTO。版本对照统一使用 huffman-aes 模式。

两类测量分开进行,两者不可互相换算:

  • 内核测量:把压缩与加密单独取出,单线程,每 8 MB 一块,反复测取最快的一次。
  • 整体测量:走完整的读、算、写流程,包含文件系统、归档格式与磁盘 I/O。

多核数据通过环境变量 SFC_WORKERS 指定工人数。未设置时工人数取硬件并行度。

3. 当前性能#

3.1 多核利用#

以计算为主的数据测纯计算能力:cpu_corpus 压缩后只有 896 MiB,往返全都落在系统内存里,几乎不碰硬盘。倍率以 1 个核心为基准。

核心数吞吐 (MiB/s)倍率多核用上了几成
1477.11.00-
2905.31.900.95
41364.92.860.72
82394.15.020.63
162863.36.000.38

计算侧上限为 6.00 倍。8 个核心到 16 个核心只多 1.20 倍,因为后 8 个是超线程,不是真核心。

换成 NCIEP 这样的真实数据,倍率大幅缩水:

核心数压缩 (MiB/s)压缩倍率解压 (MiB/s)解压倍率
1203.61.00169.41.00
8309.41.52241.31.42
16304.11.49306.11.81

单元格里的值是重复测量的平均值,1 个核心与 16 个核心各测了两次。

压缩只到 1.49 倍,解压 1.81 倍,远达不到计算侧的 6.00 倍。而且 8 个核心和 16 个核心基本一样,说明再加核心已经没有用了。

3.2 卡在哪#

两个对照实验用来分清到底是计算慢还是读写慢:

实验说明1 个核心 (MiB/s)16 个核心 (MiB/s)倍率
16 个大文件13,312 MiB 随机数据,不可压缩,其余流程相同333.8453.41.36
真实 45,390 个文件用 pack 模式,即完全不压缩也不加密290.6306.91.06

第二条是关键:完全不压缩、不加密,16 个核心的吞吐是 306.9 MiB/s,而正常压缩加密是 306.7 MiB/s,两者一模一样。计算时间被完全藏在了读写后面,一点都没体现出来。

硬盘单独跑有多快:

操作吞吐 (MiB/s)吞吐 (文件/秒)
顺序写 8 GiB 到一个文件2880-
顺序读 8 GiB 一个文件6775-
单线程读完这 45,390 个文件21006,877
只列目录和取文件信息,不读内容只受文件数限制37,825

硬盘单独读同一份数据能到 2,100 MiB/s,而程序这条路径只跑到约 306 MiB/s,差约 7 倍。

文件个数的影响也很直接:同样是 13 GiB 左右的数据,分成 16 个大文件时吞吐是 453.4 MiB/s,分成 45,390 个小文件时掉到 306.7 MiB/s,少了三成。

结论是卡在读和写这一条线,不是压缩算法。压缩算法再怎么优化也不会让程序更快。

3.3 还没做到的#

程序目前不输出读、算、写各自的分段吞吐,所以没法确定这 306 MiB/s 里各部分各占多少。上面 2,100 与 306 的 7 倍差距是两处独立测量的对比,不是同一份代码内部的分段结果;文件个数的影响也是推算,不是直接量出来的。

4. 版本演进实测#

4.1 主表#

倍率以 v2.1.0 为基准。

版本压缩 (MiB/s)压缩倍率解压 (MiB/s)解压倍率归档体积 (MiB)
v2.1.026.01.0032.81.0013,441
v2.2.0134.25.16134.34.0913,441
v2.2.1254.39.78325.19.9113,430

4.2 每一步各自贡献了多少#

倍率本身没有单位。

版本变化压缩倍率解压倍率
v2.1.0 到 v2.2.05.164.09
v2.2.0 到 v2.2.11.892.42
v2.1.0 到 v2.2.1,合计9.789.91

两段的含义不同:

  • v2.1.0 到 v2.2.0:v2.1.0 是流水线之前的串行版本。v2.2.0 把压缩和解压都改成了专职读线程、N 个计算工人、专职写线程的三段流水线,所以两边都变快。
  • v2.2.0 到 v2.2.1:结构没再动,改的是算法本身,即 Huffman 编解码重写、AES 换成标准实现走硬件、开启 LTO,见 4.3 到 4.5 节。

主表最后一行的倍率同时回答了另一个问题:当前版本比起单线程版本,压缩吞吐是 9.78 倍,解压吞吐是 9.91 倍。

4.3 压缩与加密内核单独测#

项目单位v2.1.0 与 v2.2.0v2.2.1倍率
压缩(Huffman 编码)MiB/s63.2471.17.46
解压(Huffman 解码)MiB/s91.3236.82.59
加密MiB/s62.81261.520.1
解密MiB/s62.11279.620.6
每个数据块的说明信息字节约 10232580.25

最后一行是字节数,倍率 0.25 表示降到原来的四分之一。压缩内核的倍率是 7.46,但 4.2 节里 v2.2.0 到 v2.2.1 的实际倍率只有 1.89。差距的原因就是第 1 节说的那件事:实际跑的时候,压缩已经快过读写了,再快也体现不出来。

4.4 压缩内核是分几步改的#

编码侧每一步单独看:

改动吞吐 (MiB/s)倍率
最初59.71.00
把符号表从哈希表换成数组111.11.86
再把逐位写入换成 64 位缓冲156.82.63
再加链接时优化(LTO)827.813.87

此表用的是 8 MB 均匀数据,比真实数据更容易压缩,所以数字比 4.3 节高,两组不能直接比。

解码侧:

改动吞吐 (MiB/s)倍率
v2.2.091.31.00
前述两项改动,只影响编码,解码不变92.11.01
加链接时优化114.51.25
换成 canonical 码加 12 位查表236.82.59

解码表的倍率以 v2.2.0 为基准。

4.5 加密为什么快了 20 倍#

v2.2.0 用的是自己写的 AES-CFB,纯软件计算,加密 62.8、解密 62.1 MiB/s。v2.2.1 改用 Windows 系统自带的标准 AES-128-CTR,直接走 CPU 的 AES-NI 硬件指令。选型时实测了系统提供的几种模式:

模式吞吐 (MiB/s)相对自研实现倍率结论
ECB 批量,用来生成密钥流9005143采用,最快
GCM6457103需要 28 字节前缀,与现有 16 字节 IV 布局冲突,放弃
自带 CFB加密 54.4,解密 70.50.87 与 1.13非标准且不走硬件,不可用
自带 CTR不支持-返回 STATUS_INVALID_PARAMETER

倍率以自研 AES-CFB 的 62.8 与 62.1 为基准。

5. 已知问题与边界#

项目数据说明
极端偏斜数据解压288.5 降到 215.8 MiB/s,倍率 0.7512 位定长查表每个符号的成本高于原来的两步走树

6. 数字的局限#

  • 内核数字与整体数字不能互相换算。以压缩为例,内核倍率 7.46,实际只有 1.89。
  • 吞吐量非常依赖测什么数据:容易压缩的数据与真实数据在 3.1 节内部就差了 6 倍以上,2863.3 MiB/s 对 306.1 MiB/s。
  • 4.1 节三个版本不是同一批跑的:v2.1.0 与 v2.2.1 一批,v2.2.0 与 v2.2.1 另一批。v2.2.1 在两批里分别是压缩 275.4 与 254.3、解压 345.8 与 325.1 MiB/s,批间差约 8% 与 6%。这个量级远小于版本间的差距,最小的一段也有 1.89 倍,不影响结论。
  • 压缩方向重复测很稳,两次相差不到 2%;解压方向不稳,16 个核心两次测出 275.6 与 336.6 MiB/s,差约 20%,因为解压要重新生成 45,390 个文件。3.1 节解压的 1.81 倍只能当大概量级看。

项目介绍:SFC | 开发日志:SFC | 性能优化通用方法论

SFC 性能报告:v2.1.0 / v2.2.0 / v2.2.1
https://www.yonagi.world/posts/sfc-performance-report/
作者
YONAGI
发布于
2026-09-12
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
遠い日に想いを馳せて
Laplacian
封面
遠い日に想いを馳せて
Laplacian
0:00 / 0:00