Firefox 中的 zlib-rs

Lobsters Hottest 新闻

摘要

Firefox 现在使用 zlib-rs 进行 gzip 压缩,提升了性能和安全性,不过集成时需要为 Intel Raptor Lake CPU 的一个 bug 进行变通处理。

<p><a href="https://lobste.rs/s/erc2po/zlib_rs_firefox">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/16 15:34

# Firefox 中的 zlib-rs - Trifecta Tech Foundation 来源:https://trifectatech.org/blog/zlib-rs-in-firefox/ 从151.0.0版本 (https://www.firefox.com/en-US/firefox/151.0/releasenotes/) 起,Firefox 使用 zlib-rs 进行 gzip(解)压缩。这非常令人兴奋,因为它兼具性能和安全性优势。 我们首次与 Mozilla 工程师沟通是在 2024 年夏季,但将 zlib-rs 投入生产花了整整两年时间。是什么花了这么久? ## 将 zlib-rs 集成到 Firefox 代码库 切换到 zlib-rs 并非完全微不足道:我们声称 zlib-rs 是即插即用的兼容替代品,但这个说法存在一些例外。我们更改了不同压缩级别所使用的算法(这种方式与zlib-ng (https://github.com/zlib-ng/zlib-ng) 一致,但与官方 zlib 不同),因此确切的输出字节和输出长度可能会略有变化。 Firefox 的测试套件在某些情况下会验证确切的输出字节,在更多情况下则验证(大致)输出长度。这是一个防止压缩配置出错的良好安全机制,但现在所有这些测试都需要更新。 Firefox 还给所有符号添加了前缀:它使用 `MOZ_Z_inflate` 而不是 `inflate` 来避免符号冲突。我们早就支持以多种方式给符号名称添加前缀,因此实现这一点只是配置问题。 所以需要一些工作,但改动都很直接。一切似乎都很顺利,直到…… ## Intel CPU 缺陷 我们开始看到崩溃现象 (https://bugzilla.mozilla.org/show_bug.cgi?id=1950764)。日志显示,一个边界检查在逻辑上本不应失败却失败了。当然,我们很幸运居然还能遇到边界检查失败;在 C 语言中,这只会导致静默的数据损坏。 我们无法在本地重现这个问题,但随着更多报告的涌入,一种模式开始浮现:我们的实现触发了臭名昭著的 Intel Raptor Lake CPU 缺陷。 这一代 CPU 饱受不稳定和退化问题 (https://en.wikipedia.org/wiki/Raptor_Lake#Instability_and_degradation_issue) 的困扰。我们代码中的某些部分容易触发这些问题,但我们完全不知道是什么,甚至不知道如何追踪。 最终,Fabian Giesen 撰写了“Oodle 2.9.14 和 Intel 第 13/14 代 CPU” (https://fgiesen.wordpress.com/2025/05/21/oodle-2-9-14-and-intel-13th-14th-gen-cpus/),将问题定位为一条用于将 Huffman 编码结果写入内存的特定指令。Zlib 也使用 Huffman 编码,而 zlib-rs 恰好也使用了这条有问题的指令。 尽管如此,在 Firefox 中找出并部署解决方案并非易事。今年 5 月,在 151 版本发布后不久,Mozilla 工程师推送了补丁:“一年后,Firefox 终于停止在 Intel Raptor Lake CPU 上崩溃——Mozilla 发布新版本补丁,修复 Intel 第 13 代和第 14 代 CPU 的关键缺陷” (https://www.tomshardware.com/software/mozilla-firefox/after-a-year-firefox-finally-stops-crashing-on-intels-raptor-lake-cpus-mozilla-releases-new-version-patch-critical-flaw-on-intel-13th-gen-and-14th-gen-cpus)。 ## 修复缺陷 一旦知道要找什么,修复问题就相当直接。我们有这样一个函数: https://godbolt.org/z/GjfYdPe3x `` pub fn push_dist(&mut self, dist: u16, len: u8) { let buf = &mut self.buf.as_mut_slice()[self.filled..][..3]; let [dist1, dist2] = dist.to_le_bytes(); buf[0] = dist1; buf[1] = dist2; buf[2] = len; self.filled += 3; } `` 这段代码非常简单:我们将三个字节值赋给数组的连续索引。但该函数(使用 LLVM 22 时)的汇编代码中,有一条从 `ch` 到内存的移动指令,其中 `ch` 是 RCX 寄存器的第 8-15 位: `` mov byte ptr [rsi + rdi + 1], ch `` 由于硬件缺陷,这条指令偶尔会实际写入第 0-7 位,从而导致我们看到的崩溃。 为了规避 LLVM 生成这条特定指令,我们使用了一小段 unsafe 代码(LLVM 很聪明,这是我们发现的最简单方法,能让它生成正确的结果): `` pub fn push_dist(&mut self, dist: u16, len: u8) { let buf = &mut self.buf.as_mut_slice()[self.filled..][..3]; let bytes = dist.to_le_bytes(); unsafe { buf.as_mut_ptr().cast::<[u8; 2]>().write_unaligned(bytes) } buf[2] = len; self.filled += 3; } `` Mike Hommey 在 Firefox 中的修复在这里 (https://github.com/mozilla-firefox/firefox/commit/711ef51645a2#diff-945832833d688a990ab42ad9c84ce62a5258698d92bbcabbcdaabc2efbbda282)。该补丁已被上游收录 (https://github.com/trifectatechfoundation/zlib-rs/pull/520) 到 zlib-rs 中,并且在可预见的未来我们会继续保留这个补丁:这是一小段易于审查的 unsafe 代码。这就是为了在各种平台上稳定运行而做出的牺牲。 事实证明,LLVM 23 不再生成这条有问题的指令,尽管我认为这是巧合而非有意为之。当我们把 MSRV 提高到需要 LLVM 23 的版本(例如用于自定义分配器和 C 可变参数函数)时,我们可以去掉这个变通方案。 ## 结果 那么为什么要费这么多周折呢?因为 zlib-rs 更快。快得多。尤其是在 linux x86_64 上,速度提升几乎到了荒谬的程度。这些来自zlib-py (https://github.com/Rust-for-CPython/zlib-py) 的基准测试对比了官方 zlib 和 zlib-rs: `` ------------------------------------------------------------------------- 一次性解压 ------------------------------------------------------------------------- 基准测试 CPython zlib zlib_py 加速比 ------------------------------------------------------------------------- decompress 1 KB level=1 7.1 us 1.3 us 5.66倍 更快 decompress 1 KB level=6 7.0 us 2.1 us 3.34倍 更快 decompress 1 KB level=9 7.0 us 2.1 us 3.33倍 更快 decompress 64 KB level=1 219.4 us 6.8 us 32.50倍 更快 decompress 64 KB level=6 218.6 us 7.6 us 28.70倍 更快 decompress 64 KB level=9 217.9 us 7.9 us 27.53倍 更快 decompress 1 MB level=1 3.41 ms 128.0 us 26.61倍 更快 decompress 1 MB level=6 3.42 ms 125.2 us 27.30倍 更快 decompress 1 MB level=9 3.33 ms 134.8 us 24.71倍 更快 decompress 10 MB level=1 33.95 ms 1.74 ms 19.50倍 更快 decompress 10 MB level=6 33.94 ms 1.68 ms 20.16倍 更快 decompress 10 MB level=9 33.80 ms 1.74 ms 19.42倍 更快 `` `` ------------------------------------------------------------------------- 流式解压 ------------------------------------------------------------------------- 基准测试 CPython zlib zlib_py 加速比 ------------------------------------------------------------------------- stream decompress 1 KB L6 7.3 us 2.7 us 2.74倍 更快 stream decompress 64 KB L6 221.3 us 22.7 us 9.75倍 更快 stream decompress 1 MB L6 3.36 ms 309.0 us 10.86倍 更快 stream decompress 10 MB L6 33.71 ms 3.79 ms 8.89倍 更快 `` 压缩也更快,但由于压缩比的差异,更难直接比较。 通过这些基准测试,我们注意到在 aarch64 系统上(尤其是运行 macOS 的系统)加速比较小。结果发现,Apple 提供了更优化的 zlib 动态库,它在一些对性能最敏感的部分使用了内联汇编。这让我们意识到我们错过了一些优化,目前我们正在整合它们。 ## 结论 升级到 zlib-rs 应该很直接,但在这个案例中,我们遇到了迄今为止最难缠的缺陷。对于 CPU 缺陷,我们几乎没有线索可循,标准的调试工具也价值不大。我们花了几个月时间,真的不知道该做什么,但现在我们有了一个变通方案,终于可以继续前进了。 我们非常兴奋,因为 zlib-rs 现在为更多用户提供服务。我们想感谢 Mozilla,特别是 Mike Hommey 和 Gabriele Svelto,感谢他们的集成工作以及追踪修复这个 CPU 缺陷。 ---

相似文章

宣布推出Rust版Zstandard

Lobsters Hottest

Trifecta Tech Foundation 宣布首次发布 libzstd-rs-sys,这是一个纯 Rust 实现的 Zstandard 压缩格式,可作为 C 参考实现的直接替代品,在轻微性能损失下提供更好的可移植性和内存安全性。

OpenZL

Lobsters Hottest

OpenZL 是一个压缩库,能够为特定数据格式生成专门的压缩器,以高速实现高压缩比,适用于数据中心工作负载,如 AI 处理。