Jason Evans:jemalloc的开源20年回忆录

jemalloc 事后回顾

jemalloc 内存分配器的构想始于 2004 年初,至今已公开使用了约 20 年。得益于开源软件许可的特性,jemalloc 将永久保持公开可用。但是,其上游的积极开发已经走到了尽头。

本文将简要描述 jemalloc 的几个发展阶段,每个阶段都包含一些成功与失败的亮点,最后附上一些回顾性评论。

阶段 0:Lyken

2004 年,我开始在科学计算的背景下开发 Lyken 编程语言。

Lyken 最终成了一个死胡同,但它的手动内存分配器到 2005 年 5 月时功能已经完备。(本应利用其特性的垃圾回收器却从未完成。)

2005 年 9 月,我开始将该分配器集成到 FreeBSD 中,并于 2006 年 3 月将其从 Lyken 中移除,转而使用系统分配器功能的薄封装层。

为什么在投入如此多精力后,还要将内存分配器从 Lyken 中移除呢?

嗯,一旦该分配器被集成到 FreeBSD 中,就发现系统分配器唯一缺少的功能是一个用于跟踪分配量以触发每线程垃圾回收的机制。

而这个功能可以通过使用线程特定数据和 dlsym(3) 的薄封装层来实现。有趣的是,多年以后,jemalloc 甚至添加了 Lyken 当初所需要的统计数据收集功能。

阶段 1:FreeBSD

回到 2005 年,向多处理器计算机的过渡正在进行中。FreeBSD 当时拥有 Poul-Henning Kamp 出色的 phkmalloc 内存分配器,但该分配器没有为并行线程执行做任何准备。

Lyken 的分配器似乎是一个明显的伸缩性改进方案,在朋友和同事的鼓励下,我集成了这个很快被称为 jemalloc 的项目。

啊,但事情没那么简单!集成后不久,就发现 jemalloc 在某些负载下存在严重的碎片化问题,特别是那些由 KDE 应用程序引起的负载。

正当我以为我差不多完工时,这个现实世界中的失败让 jemalloc 的可行性受到了质疑。

简而言之,碎片化问题源于使用统一的区段(extent)分配方法(即没有按大小类别进行隔离)。

我从 Doug Lea 的 dlmalloc 中获得了基本灵感,但没有采用其久经考验、相互交织的启发式算法,而这些算法避免了许多最严重的碎片化问题。

随后是大量的疯狂研究和实验。当 jemalloc 成为 FreeBSD 发行版的一部分时,其布局算法已经完全改变,转而使用按大小隔离的区域,正如在 2006 年 BSDCan 的 jemalloc 论文中所描述的那样。

阶段 1.5:Firefox

2007 年 11 月,Mozilla Firefox 3 即将发布,而高碎片化是一个尚未解决的问题,尤其是在 Microsoft Windows 上。

由此开启了与 Mozilla 在内存分配方面长达一年的合作。将 jemalloc 移植到 Linux 非常简单,但 Windows 则是另一回事。

jemalloc 的标准源码在 FreeBSD 的 libc 库中,所以我们基本上是 fork 了 jemalloc 并添加了可移植性代码,同时将任何与 FreeBSD 相关的改动上游化。

整个实现仍然在一个文件中,这减少了维护 fork 的摩擦,但在此开发阶段,实现的复杂性绝对超过了单个文件所能合理承载的范围。

多年后,Mozilla 的开发者们为上游的 jemalloc 做出了重大贡献,试图摆脱他们的 fork 版本。

不幸的是,Mozilla 的基准测试一直显示,fork 后的版本性能优于上游版本。我不知道这是由于对局部最优解的过拟合,还是真正表明了性能衰退,但这仍然是我对 jemalloc 最大的失望之一。

阶段 2:Facebook

2009 年我开始在 Facebook 工作时,惊讶地发现,在 Facebook 基础设施中普及 jemalloc 的最大障碍是工具化(instrumentation)。

关键的内部服务处于一种尴尬境地:它们依赖 jemalloc 来控制内存碎片,但工程师们又需要用 tcmalloc 和 gperftools 套件中的 pprof 堆分析工具来调试内存泄漏。pprof 兼容的堆分析功能成为了 jemalloc 1.0.0 版本的头条特性。

jemalloc 的开发迁移到了 GitHub,并在接下来的几年里,随着问题和机遇的出现而零星地进行着。其他开发者开始贡献重要的功能。

3.0.0 版本引入了广泛的测试基础设施,以及对 Valgrind 的支持。

4.x 系列版本引入了基于衰减的内存清除(decay-based purging)和 JSON 格式的遥测数据。

5.x 系列则从"块"(chunks)过渡到"区段"(extents),为更好地与 2 MiB 巨页(huge pages)交互铺平了道路。

有点争议的是,我在 5.0.0 版本中移除了对 Valgrind 的支持,因为它是一个重大的维护难题(在许多微妙的地方有无数的牵连),而且在 Facebook 内部也无人使用;

像 pprof 和 MemorySanitizer 这样的其他工具占据了主导地位。我当时收到的关于 Valgrind 支持的反馈非常少,因此推断它并未被广泛使用。

现在回想起来,情况似乎并非如此。特别是,Rust 语言直接将 jemalloc 集成到其编译的程序中,我认为 Rust 开发者和 Valgrind 开发者之间存在一些交集。

人们对此感到愤怒。jemalloc 可能因此比其自然发展轨迹本应的时间更早地被从 Rust 的二进制文件中移除了。

Facebook 的内部监控系统令人叹为观止,能够从无数服务中获得性能数据,对内存分配器的开发来说是一大福音。

我认为过去十年中两个最快的内存分配器(tcmalloc 和 jemalloc)都受益于这些数据,这并非偶然。

即使是像"快速路径"(fast-path)优化这样"简单"的事情,当手头有聚合的 Linux perf 数据时,也更容易做对。

像避免碎片化这样更难的问题依然困难,但如果数千个不同的工作流表现良好且没有异常的性能衰退,那么一个变更就可能是安全的。

jemalloc 作为 Facebook 基础设施的组成部分,在性能、弹性和行为一致性方面受益匪浅。

此外,jemalloc 自带的集成统计报告功能,正是为了响应这种无处不在的遥测环境而产生的,事实证明,这对 jemalloc 的开发和非 Facebook 应用的调优/调试都带来了远超实现成本的巨大好处。

在 Facebook 的最后一年,我被鼓励组建一个小的 jemalloc 团队,这样我们就可以处理一些否则会令人望而生畏的大任务。除了重大的性能改进,我们还实现了持续集成测试和全面的监控。

当我 2017 年离开 Facebook 时,jemalloc 团队在我的杰出同事王奇(Qi Wang)的领导下,以及众多其他贡献者的出色工作(正如提交历史所证明的那样),继续进行了数年优秀的开发和维护工作,而我几乎没有参与。

阶段 3:Meta

jemalloc 的开发性质在 Facebook 更名为 Meta 之后发生了显著变化。

Facebook 的基础设施工程减少了对核心技术的投资,转而强调投资回报率。

这一点在 jemalloc 的提交历史中显而易见。特别是,有原则的巨页分配(HPA)的种子早在 2016 年就已播下!

HPA 的工作持续了几年,然后放缓,最终停滞不前,因为各种修补方案不断堆积,却没有进行保持代码库健康所必需的重构。

这一功能的发展轨迹最近已然崩塌。由于我已多年未曾密切参与,这份心碎对我来说有所减轻,但由于 Meta 内部最近的变化,我们再也没有人能够以通用性为目标来引导 jemalloc 的长期发展了。

我不想纠缠于戏剧性的情节,但或许值得一提的是,尽管大多数相关人员都是出于善意行事,jemalloc 在 Facebook/Meta 手中的结局依然是悲伤的。

企业文化会随着内外部压力而转变。人们会发现自己陷入无法摆脱的困境,主要选择只有:1)在巨大压力下做出糟糕的决定,2)在巨大压力下屈从,或者 3)被绕开。

作为个体,我们有时有足够的影响力来减缓组织的退化,甚至可能在局部促成复兴,但我们谁也无法阻止那不可避免的结局。

我仍然非常感谢我以前的同事们在 jemalloc 上所做的所有出色工作,也感谢 Facebook/Meta 长期以来投入如此之多。

阶段 4:停滞

现在怎么办?就我而言,"上游"的 jemalloc 开发已经结束。

Meta 的需求在一段时间前就已与外部用户的需求不再完全一致,他们自己单干会更好。

如果我要重新参与,第一步将是至少数百小时的重构,以偿还累积的技术债务。而我对这之后的工作并没有足够的兴趣来支付如此高昂的前期成本。

也许其他人会创建可行的 fork,无论是从 dev 分支还是从已经三年之久的 5.3.0 版本开始。

在前面的章节中,我提到了几个特定阶段的失败,但还有一些普遍性的失败让我感到惊讶,尽管我的职业生涯一直专注于开源开发。

如前所述,移除 Valgrind 引起了一些负面情绪。但问题的根源在于对外部用途和需求缺乏了解。

如果我知道它对任何人很重要,我可能会与他人合作保留 Valgrind 的支持。再举个例子,我可能在 jemalloc 被用作 Android 内存分配器大约两年后才完全意识到这件事。又过了几年,直到事后我才知道它被替换了。

尽管 jemalloc 的开发始终是完全开放的(没有被孤立在 Facebook 内部),但这个项目从未发展到能留住来自其他组织的主要贡献者。

Mike Hommey 推动 Firefox 转向使用上游 jemalloc 的努力,差一点就成功了。其他人尝试将构建系统过渡到基于 CMake 的努力也多次停滞,从未冲过终点线。

我从 Darwin 的惨痛经历中知道,内部孤立的开源项目无法茁壮成长(HHVM 是一个重复的教训),但 jemalloc 要想作为一个独立项目蓬勃发展,需要的不仅仅是开放开发。

jemalloc 对我来说是一次奇特的"弯路",因为 25 年多来,我一直是垃圾回收(GC)而非手动内存管理的坚定支持者。

就我个人而言,我很高兴能再次从事于垃圾回收的系统,但 jemalloc 是一个极具成就感的项目。感谢所有让这个项目如此有价值的人,包括合作者、支持者和用户。

参考

jasone.github.io/2025/06/12/...

相关推荐
修己xj2 小时前
MAZANOKE:一款隐私优先的浏览器图像优化工具及Docker部署指南
开源
G探险者3 小时前
为什么 Zookeeper 越扩越慢,而 Nacos 却越扩越快?
分布式·后端
不太厉害的程序员3 小时前
NC65配置xml找不到Bean
xml·java·后端·eclipse
不被定义的程序猿3 小时前
Golang 在 Linux 平台上的并发控制
开发语言·后端·golang
AntBlack3 小时前
Python : AI 太牛了 ,撸了两个 Markdown 阅读器 ,谈谈使用感受
前端·人工智能·后端
mikes zhang4 小时前
Flask文件上传与异常处理完全指南
后端·python·flask
Pitayafruit4 小时前
跟着大厂学架构01:如何利用开源方案,复刻B站那套“永不崩溃”的评论系统?
spring boot·分布式·后端
方圆想当图灵4 小时前
深入理解软件设计:领域驱动设计 DDD
后端·架构
excel4 小时前
MySQL 9 在 Windows 上使用 mysqld --initialize-insecure 无响应的排查与解决方案
后端
你怎么知道我是队长5 小时前
GO语言---defer关键字
开发语言·后端·golang