DEVTOOLBOX / BENCHMARKS

实测数据:哈希、Base64、gzip、JSON、PDF 与图片编码

下面每个数字都由本仓库的脚本在同一台机器上跑出,机器信息放在最前面。重点不是绝对值,而是分清哪些代价是结构性的(Base64 恒定膨胀三分之一),哪些是硬件相关的(这里 SHA-256 反而比 MD5 快)。

实测日期 Apple M5 · macOS 27.0.1 · Node v20.20.2

在哪里、用什么方法测的

所有耗时都取 3–5 次运行的中位数,且在空闲机器上完成,每一行都来自同一台机器。这里没有任何数字是从别处抄来或估算的:脚本自己构造输入(随机缓冲区、合成记录数组、现场生成的 PDF、canvas 渲染的图片),报告它实际观察到的时间。

这件事很重要,因为网上流传的基准数字通常不可比。一个没有写明算法、数据量、运行时版本与硬件的吞吐数字,不是测量结果,只是轶事。你在自己机器上重跑这个脚本,比值应该接近,绝对速度会不同——有时差一个数量级。

项目值
CPUApple M5
核心数 / 内存10 / 32 GB
操作系统macOS 27.0.1
运行时Node v20.20.2
浏览器(图片实测)Chromium 151.0.7922.34
数据生成日期2026-10-22

哈希吞吐:这里 SHA-256 比 MD5 快

这是全页最反直觉的一行。在这台机器上,SHA-256 处理同一块 64 MB 缓冲区大约比 MD5 快 3.7 倍,SHA-1 与 SHA-256 基本持平。原因不是 SHA-256「更轻」,而是现代 ARM 芯片带有专门的 SHA-1/SHA-256 指令:被硬件加速的算法几乎免费,而没有硬件路径的算法只能靠通用指令硬算。

由此得出两个实用结论。第一,不要因为「MD5 更快」就选 MD5,在当前硬件上这个前提常常是错的,要用实测说话。第二,本来也不该用 MD5——理由是抗碰撞性已经失守,与速度无关。SHA-512 在这里居中,是因为它按 64 位字处理:在 64 位 CPU 上每字节参与的字更少,但没有宽位硬件支持时达不到 SHA-256 的速率。

算法数据量耗时吞吐
MD564 MB82 ms778 MB/s
SHA164 MB22 ms2,922 MB/s
SHA25664 MB22 ms2,910 MB/s
SHA51264 MB39 ms1,648 MB/s

吞吐是硬件与运行时的属性,不是算法名字的属性。用速度做理由之前先实测。

Base64:4/3 这条规则是精确的,可以提前算出来

每一行都落在 1.3333(只有极小的输入受填充影响而略高:1024 字节得到 1368 个字符,比值 1.3359)。公式是 ceil(bytes / 3) × 4,也就是说不用编码就能预估长度。10 MB 文件会变成 13.98 MB;一张 5 MB 的截图内嵌到 CSS 会成为 6.67 MB 样式表,每次加载都要解析。

填充那一列是实操陷阱:只要原长度不是 3 的倍数,就会出现一到两个 =,而严格解码器会拒绝长度不是 4 的倍数的载荷。所以拼接两段 Base64、或把它按行折行,都会破坏这个不变量——带换行的载荷在解码前必须去掉空白。

原始大小编码后膨胀比填充
1.0 KB1.3 KB1.33592 × "="
64.0 KB85.3 KB1.33342 × "="
1.00 MB1.33 MB1.33332 × "="
10.00 MB13.33 MB1.33332 × "="

编码后长度 = ceil(字节数 / 3) × 4。放不进预算时,答案是压缩或改走二进制通道,而不是「用小一点的 Base64」。

gzip:JSON 压到七分之一,随机字节一点也压不动

结构化、重复性高的数据压缩收益极大:302 KB 的 JSON 数组压到 41 KB,比值 0.137;重复文本收益更大。而随机字节基本停在 1.0 附近——gzip 找不到不存在的冗余,只剩几字节的帧开销。

发布接口或可下载资源前值得养成这个检查习惯:JSON、HTML、CSS、SVG、日志几乎总是值得压缩;已压缩格式(PNG、JPEG、ZIP、gzip 自身)再压只烧 CPU 没有收益。这也解释了为什么 Base64 放在经过 gzip 的 HTTP 响应里,比落盘保存要「没那么浪费」:体积膨胀依然存在,但编码后的文本本身可被压缩。

样本原始大小gzip 后压缩比
JSON array (5000 records)295.1 KB40.4 KB13.7%
Repeated text87.9 KB342 B0.4%
Random bytes256.0 KB256.1 KB100.0%

只在正确的层压缩一次。重复压缩已压缩格式,只是浪费 CPU。

JSON 规模:十万条记录解析是几十毫秒的量级

解析 10.4 MB 的 JSON 约 33 ms,把同样的结构转回字符串约 17 ms——快到「CPU 太慢」很少是大 JSON 把标签页搞崩的原因。真正致命的是内存与主线程:解析出的对象图占用文本体积的数倍,而一次同步解析会在整个过程中阻塞渲染。

这正是 JSON Lines 存在的理由。把载荷拆成一行一个对象,每行都是可以独立解析、用完即弃、出错可续的单元——用略大的总字节数换取恒定内存与非阻塞的流水线。一次性粘贴 10 MB,数组更省事;面对 2 GB 的导出,这就不是选择题了。

记录数字节数JSON.parseJSON.stringify
1,00095.9 KB0.3 ms0.2 ms
10,000988.0 KB2.6 ms1.9 ms
100,0009.93 MB33.3 ms17.2 ms

解析时间随字节数线性增长,所以要看字节而不是猜上限。真正先撑不住的是内存,不是 CPU。

PDF 合并:复制页面很便宜,页面内容才是体积来源

合并 200 页现场生成的 PDF 约 24 ms,因为这个操作只复制页面对象、从不栅格化:文字仍是文字,图片保持原有编码。这里 200 页产物只有 39 KB,因为输入是合成的——每页只有一行短文本。

真实场景的合并行为值得记住:耗时随页面与对象数量增长,但文件大小由页面里原有的图片决定。两份 50 MB 的扫描件合并同样很快,结果仍接近 100 MB。所以「合并很快」和「合并后很小」是两件不相关的事,只有前者是关于合并本身的。

输入合并后页数耗时产物大小
2 × 5 页106 ms2.5 KB
5 × 20 页10016 ms19.7 KB
10 × 20 页20024 ms38.8 KB

合并保留内容、不重新编码。产物体积大致等于各输入之和。

图片编码:同一张 1600×1200 的图,六种存法

同样的 canvas 内容存成 PNG 是 2.36 MB;JPEG 质量 0.75 是 80 KB;WebP 质量 0.75 是 39.6 KB——相对 PNG 缩小 59 倍。PNG 是无损的,因此把测试图里的每个噪点都保留下来,这也解释了为什么「照片截图」体积爆炸、「纯文字截图」却很小。

由此得到的判断规则:照片与渐变图走 WebP(兼容性要求时用 JPEG),只有带锐利边缘、需要透明度、或文字必须像素级精确的图才留在 PNG。另外注意质量参数不是线性的——WebP 从 0.9 降到 0.75 体积减半,而从 0.75 降到 0.5 只再省四分之一,代价是渐变处肉眼可见的模糊。

格式质量文件大小相对 PNG
PNG无损2.25 MB1.000×
JPEG0.92159.2 KB0.069×
JPEG0.7578.3 KB0.034×
WebP0.9075.8 KB0.033×
WebP0.7538.7 KB0.017×
WebP0.5029.6 KB0.013×

选格式让体积差一个数量级;调质量只能在两倍以内挪动。

这些数字不能告诉你的事

测量方法: each timing is the median of 3–5 runs on an otherwise idle machine;Node crypto over a single 64 MB random buffer, digest included;random bytes encoded with Buffer.toString("base64");zlib.gzipSync at level 6;synthetic record arrays, JSON.parse and JSON.stringify timed separately;pdf-lib creating pages with one text run each, then copyPages + save;canvas.toBlob inside headless Chromium; a 1600×1200 gradient with 4000 noise specks.