Loading... > 本文翻译自 [jemalloc Postmortem](https://jasone.github.io/2025/06/12/jemalloc-postmortem/),原作者 [Jason Evans](https://github.com/jasone) [jemalloc 内存分配器](https://jemalloc.net/) 最早构思于 2004 年初,并且已经公开使用了大约 20 年。由于开源软件许可的性质,jemalloc 将会永久保持公开可用,但是上游的活跃开发已经结束。本文简要描述 jemalloc 的开发阶段,每个阶段都有些成功或失败的亮点,最后附有一些回顾性的评论。 ## Phase 0: 源于 Lyken 2004 年的时候,我开始在科学计算的背景下开发 Lyken 编程语言。这门语言最终挂了,但是它的手动内存分配器到 2005 年 5 月已经功能完整。(不过,原本打算在其上实现的垃圾收集器从未完成)2005 年 9 月,我开始将分配器集成到 FreeBSD 中,到 2006 年 3 月,我移除了 Lyken 中的分配器,转而使用系统分配器的一个简单封装版本。 为什么在投入了这么多努力之后,还要移除从 Lyken 中集成的内存分配器?其实我们刚把这个分配器集成到 FreeBSD 中,就发现其实系统分配器所唯一缺少的就是一个统计、跟踪内存分配量的功能,目的是能够触发线程级别垃圾回收。而这可以通过使用线程特定数据和 [dlsym(3)](https://www.man7.org/linux/man-pages/man3/dlsym.3.html) 的简单包装来实现。有趣的是,许多年后 jemalloc 添加了 Lyken 当初缺少的统计收集功能。 ## Phase 1: 进入 FreeBSD 时间回到 05 年,FreeBSD 当时拥有 Poul-Henning Kamp 设计的卓越的 phkmalloc,但是这个分配器没有提供多线程环境的合适规定,而 Lyken 的分配器看起来是一个明显的改进,并且颇具可扩展性。在朋友和同事的鼓励下,我把它集成了过来——很快,人们开始把它称作 jemalloc。但这进展有点儿太快了——集成后不久,我们就明显发现 jemalloc 在一些负载下存在严重的碎片化问题,尤其是由 [KDE](https://kde.org/) 应用程序引起的那些。就在我以为 jemalloc 的迁移差不多胜利了的时候,这个现实场景中的失败让 jemalloc 的可行性受到了质疑。 简要来说,碎片化问题源于使用统一的范围分配方法(即没有大小类分离)。我从 Doug Lea 的 [dlmalloc](https://gee.cs.oswego.edu/pub/misc/malloc.c) 中获得了基本灵感,但没有采用那些经过实战检验的交织启发式算法,这些算法避免了许多最严重的碎片化问题。随后我们在这方面疯狂搞研究和实验。到 jemalloc 成为 FreeBSD 版本的一部分时,其布局算法已经完全改变,改为使用大小分离的区域,如 [2006 年 BSDCan jemalloc 论文](https://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf)中所述。 ## Phase 1.5: 整合 Firefox 到了 07 年11月,[Mozilla Firefox](https://www.mozilla.org/en-US/firefox/new/) 3即将发布,高度碎片化是一个未解决的问题,尤其是在微软的 Windows 上。因此,我们花了一年时间与 Mozilla 合作处理内存分配问题。将 jemalloc 移植到 Linux 很简单,但 Windows 就是另一码事了。jemalloc 的官方源代码在 FreeBSD libc 库中,所以我们基本上分叉了 jemalloc 并添加了可移植性代码,将任何与 FreeBSD 相关的代码都推送到上游。整个实现仍然在一个文件中,这减少了分叉维护的困难,但肯定是在这个阶段中的某个时间,整个实现的复杂性已经超出了单文件所能承载的范围了。 多年后,Mozilla 开发者对上游 jemalloc 做出了重大贡献,以摆脱他们需要自己维护一个专门分支的处境。但不幸的是,Mozilla 的基准测试一直显示,他们的分支版本优于上游版本。我不知道这是否是过拟合的问题,还是说性能上存在实际的差异,但这仍然是我对 jemalloc 留有的最大遗憾之一。 ## Phase 2: 在 Facebook 在 2009 年加入 Facebook 工作后,我惊讶地发现阻碍 Facebook 基础设施中广泛使用 jemalloc 的最大障碍是监控。关键的内部服务处于一种尴尬的境地:我们依赖 jemalloc 来控制内存碎片,但工程师们需要使用 [tcmalloc](https://github.com/google/tcmalloc) 和 [gperftools](https://github.com/gperftools/gperftools) 中的`pprof`堆分析工具来调试内存泄漏。因此兼容`pprof`的堆分析功能就成了 jemalloc 1.0.0 版本的重中之重。 jemalloc 开发迁移到了[GitHub](https://github.com/jemalloc/),接下来几年中,随着不断出现的问题和机遇,开发断断续续地进行着。其他开发者开始贡献重要的功能。我们在 3.0.0 版本引入了广泛的测试基础设施,以及对 [Valgrind](https://valgrind.org/) 的支持。4.x 系列引入了基于衰减的清除机制和 [JSON](https://en.wikipedia.org/wiki/JSON) 格式的可观测性能力。5.x 系列为了更好地与 2MiB 的巨页交互,我们从块(chunk)迁移到了块范围(extent)。 在某种程度上更具争议的是,我在 5.0.0 中移除了对 Valgrind 的支持,因为它在维护上非常麻烦(在许多细节处缠绕不清),而且在 Facebook 内部并未被真正使用——其他工具如`pprof`和 [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html) 占据了主导地位。我几乎没有收到过关于 Valgrind 支持的反馈,因此推断它并未被使用。其实回顾起来,情况似乎并非如此。特别是,[Rust 语言](https://www.rust-lang.org/)直接将 jemalloc 集成到程序中,我认为 Rust 开发者和 Valgrind 开发者之间存在一定的重叠。人们对此感到愤怒。jemalloc 可能被加速排除在了 Rust 二进制文件之外。(译者注:这是说移除了对 valgrind 的支持后,Rust 如果继续使用 jemalloc,加上 valgrind 进行分析时就玩不转了) Facebook 内部的遥测令人叹为观止,拥有来自众多服务的性能数据来指导内存分配器开发是一大福音。过去十年中速度最快的两个内存分配器(tcmalloc 和 jemalloc)都受益于这些数据,这并非巧合。即使像“简单”的快速路径优化,在有汇总的 [Linux perf](https://en.wikipedia.org/wiki/Perf_%28Linux%29) 数据的情况下也更容易做对。像避免碎片化这样的难题仍然很难,但如果数千个不同的工作流程表现良好,没有异常回归,那么改变大概率就是安全的。jemalloc 作为 Facebook 基础设施的性能、弹性、一致性支持的一部分,受益匪浅。此外,jemalloc 自带的集成统计报告功能天生能够匹配这些常规的监控需求,并且我们最终发现这通常对 jemalloc 开发和 Facebook 以外的应用程序调优/调试的益处远超过实现它所付出的努力。 在我 Facebook 的最后一年,我被鼓励组建一个小型的 jemalloc 团队,以便我们能够处理一些原本令人生畏的大任务。除了主要的性能改进之外,我们还实现了 CI 测试和全面的遥测。当我 2017 年离开 Facebook 时,jemalloc 团队在尊敬的同事 Wang Qi 的领导下,基本上没有我的参与下,已经连续进行了好几年出色的开发和维护工作。正如提交历史所证明的那样,这里面还有许多其他人的卓越贡献。 ## Phase 3: 变为 Meta jemalloc 开发的性质在 Facebook 重新更名 Meta 之后明显发生了变化。Facebook 基驾减少了对核心技术的投资,转而更加注重投资回报。这在 jemalloc 的提交历史中表现得非常明显。特别是,关于原则性的大页分配(HPA)的种子早在 2016 年就已经播下了!然而 HPA 的工作持续了几年,然后逐渐放缓,最终因为缺乏必要的重构来保持代码库健康,各种调整调整互相叠加,进入了停滞的局面。这一功能轨迹最近出现彻底崩溃了。对我来说,这种失望之情在某种程度上被钝化了,因为我已经多年没有密切参与其中,但最近 Meta 内部的变化导致我们不再有人能够长期指导 jemalloc 的发展,以期实现普遍的实用性。 我不想过多讨论戏剧性的事情,但也许值得提到的是,尽管大多数参与者都是出于善意,但 jemalloc 在 Facebook/Meta 的手中最终还是走到了一个悲伤的终点。企业文化会根据内外部压力的变化而变化。人们会发现自己陷入了两难的抉择:要么在极端压力下做出糟糕的决定,要么在极端压力下屈从,要么干脆被绕过。作为个人,我们有时有足够的影响力来减缓组织的退化,甚至可能为某种孤立的复兴做出贡献,但没有人能够逆转这些悲剧发生的趋势。 我非常感激我的前同事们在 jemalloc 上所做的出色工作,也感谢 Facebook/Meta 长期以来的巨大投资。 ## Phase 4: 停滞 现在怎么办?就我个人而言,“上游” jemalloc 的开发已经结束了。Meta 的需求很久以前就已经不再与外部使用的需求很好地对齐了,他们最好自己做。如果我要重新参与进来,第一步至少需要几百个小时的重构来偿还累积的技术债务。而我对之后的前景并不足够兴奋,不愿意支付如此高的前期成本。或许其他人会创建可行的分支,无论是从 `dev` 分支还是从 5.3.0 发行版(已经三年前的版本了)! 在前面的部分中,我提到了几种特定阶段的失败,但在专注于开源开发的整个职业生涯中,我还是对一些一般的问题感到惊讶: * 正如前面提到的,移除 Valgrind 当时引起了不少负面情绪。但问题的根本在于对外部使用和需求缺乏意识。如果当初我知道有人关心这个问题,我可能会与其他人一起努力保留 Valgrind 的支持。另一个例子是,我大概有两年时间完全不知道 jemalloc 在 Android 操作系统中作为内存分配器的使用情况。多年后,同样在不知情的情况下,jemalloc 被替换了。 * 尽管 jemalloc 的开发完全公开(并非在Facebook内部封闭),该项目从未发展到保留来自其他组织的主要贡献者。由 Mike Hommey 驱动的,Mozilla 推动 Firefox 迁移到上游 jemalloc 的努力功亏一篑。其他人尝试过渡到基于 [CMake](https://cmake.org/) 的构建系统的努力多次受阻,也从未完成过。我从与 [Darwin](https://en.wikipedia.org/wiki/Darwin_(operating_system)) 经历过的艰难经验中认识到,内部封闭的开源项目无法繁荣([HHVM](https://hhvm.com/)是一个重复的教训),但 jemalloc 作为一个独立项目要繁荣,需要的不仅仅是开发层面的开放。 jemalloc 对我来说是个奇怪的转变,因为我超过 25 年来一直是垃圾回收的强烈支持者。我个人很高兴再次投入到垃圾回收系统的开发中,但 jemalloc 是一个非常令人满足的项目。感谢所有使这个项目如此有价值的人,包括所有合作者、支持者和用户。 © 允许规范转载 打赏 赞赏作者 赞 如果觉得我的文章对你有用,请随意赞赏