Hyaika Blog

Penguin is all you need

技术

+229****4539.81%:一个被媒体放大了 2 万倍的内核补丁,和它真实的 24%

+229****4539.81%:一个被媒体放大了 2 万倍的内核补丁,和它真实的 24%

+2294539.81%:一个被媒体放大了 2 万倍的内核补丁,和它真实的 24%

目录

  • 先看这个数字:+229****4539.81%
  • sheaves 和 barn:内核内存分配器里的「货架」和「谷仓」
  • 为什么基准值越小,百分比越离谱
  • 81 行代码,改的其实是「宁可用旧货,也不腾地方」的毛病
  • 现场验证:我的服务器里,170 万个内存对象是怎么住的
  • 百分比是放大镜,代码是显微镜

先看这个数字:+229****4539.81%

如果你今天早上刷到了 Phoronix 的标题,你看到的是这样的:

MM Change Slated For Linux 7.4 Yields +229****4539.81% In One Metric

百分比放大镜:同一个收益,三种说法

六个数字,中间被打上了星号。229 万个百分点。

这个数字大得离谱,大得 Phoronix 自己都不敢把完整的数字写进标题——他们在标题里把中间几位打码了,像什么需要审查的敏感信息。点进去看正文,完整数字是 +2294539.81%,也就是约 2.29 万倍。

我第一反应是:这 patch 是重写了内存分配器吗?81 行代码能让一个操作快两万倍?

然后我算了一笔账,发现这个数字比它看起来小得多。


sheaves 和 barn:内核内存分配器里的「货架」和「谷仓」

先说背景。Linux 内核里有个东西叫 slab 分配器,负责给内核里那些频繁创建销毁的小对象(文件描述符、inode、dentry 这些)分配内存。

去年内核里引入了一个新层叫 sheaves。你可以把它想成每个 CPU 自己专用的「货架」——数组式缓存,对象先放货架上,用的时候直接拿,不用每次都去翻总仓库。

而 barn(谷仓)是货架背后的仓库,存着「装满了的货架」和「空货架」。

补丁作者 Hao Li 在提交说明里解释了这个优化:

当前,当 prefill API 重新装满一个不满的 sheaf 时,它只从部分 slab 里取对象,从不从 barn 里那些「满的 sheaves」里取——所以一旦 barn 的满列表饱和了,它就永远饱和。

对于通过 kfree_rcu() 释放的对象,每个 RCU sheaf 都得被刷回 slab,因为 barn 的满列表没有空位了。

修复方法:让 sheaf 优先从 barn 里补充,并在 barn 里引入一个「部分 sheaf」来存放剩余对象。

翻译成人话:谷仓满了就永远满,货架空了只能去更远的仓库搬货,明明自家后院就有存货却不去拿。

这就像你家冰箱塞满了没吃完的外卖(full sheaves),但每次做饭你还是要下楼去超市买食材(partial slabs),因为冰箱「满了」——不是没东西,是没地方放新东西。结果就是冰箱里的剩菜越放越久,你跑超市跑得越来越勤。


为什么基准值越小,百分比越离谱

现在到了这篇文章最核心的部分:那个 +2294539.81% 到底意味着什么?

我做了个简单计算。如果优化后某个操作每秒发生 100 次,那么优化前它是:

100 / (1 + 2294539.81/100) ≈ 0.00436 次/秒

也就是每 22946 秒一次,约每 6.4 小时才发生一次。

一个每 6 小时才发生一次的操作,变成了每 0.01 秒一次——在 benchmark 的计时器看来,这就是「快了 2 万倍」。

但换个角度想:一个每 6 小时才执行一次的操作,就算优化前要花 1 毫秒,它对你的系统总开销贡献是 1ms / 6h = 0.0000046%。优化后它变成 0.01 秒一次,每次还是 1 毫秒,总开销变成了 1ms / 0.01s = 10%——等等,这不对。

哦,我算错了。重新来。

基准测试测的是吞吐量(每秒能完成多少次操作)。如果 barn_get 这个操作从「每 6 小时一次」变成「每秒 100 次」,那不是它变快了,是它被调用的次数变多了——因为 prefill 路径现在能从 barn 直接拿货,不需要走慢路径。

这就是百分比放大镜的第一层真相:

百分比不衡量「收益」,它衡量「基准值的大小」。基准值趋近于零时,任何改进都是无限百分比。

那个 +2142544.07%(约 2.14 万倍)的 barn_put 同理——barn 里现在有位置放东西了,put 操作从「几乎永远失败」变成「经常成功」。

Phoronix 自己在标题里打码,说明他们也知道这个数字会误导人。但他们还是用了它,因为它足够吸引眼球。我猜这就是「百分比新闻学」:标题负责震撼,正文负责澄清,而大部分读者只看标题。


81 行代码,改的其实是「宁可用旧货,也不腾地方」的毛病

那真正的收益是多少?Phoronix 的基准测试显示:

  • will-it-scale mmap1 测试:整体 +24%
  • barn_get:+2294539.81%
  • barn_put:+2142544.07%
  • 其他指标:双位数百分比提升

+24%。这才是值得写进 commit message 的数字。

81 行代码换来 mmap 场景 24% 的吞吐提升,对内核来说是很扎实的优化——尤其是它修的还是一个逻辑 bug 式的行为:barn 满了就永远满,宁可让 kfree_rcu() 的对象都刷回 slab 也不给 barn 腾地方。

这个 bug 的影响是累积性的:系统跑得越久,barn 的满列表越饱和,prefill 路径越频繁地走慢路径。就像冰箱越塞越满,你下楼买菜的频率越来越高,最后冰箱里的剩菜都馊了还得扔——而这一切本可以用一个「先清冰箱再买菜」的规则避免。

Hao Li 的补丁核心就两件事:

  1. prefill 时先看 barn:从 barn 里拿满的 sheaf 补充,腾出满列表的空间
  2. barn 里引入 partial sheaf:从 barn 拿货剩下的对象放这里,而不是扔回 slab

这是 81 行代码,没有新数据结构,没有复杂算法——就是改了取货顺序。


现场验证:我的服务器里,170 万个内存对象是怎么住的

我没有 Linux 7.4(我的服务器跑的是 6.8.0-124-generic,内核版本差了好几个大版本),所以没法直接跑那个补丁。但 slab 分配器在我机器上是活的,我可以看看它真实的样子:

我的服务器:170 万个 slab 对象

$ uname -r
6.8.0-124-generic

$ head -1 /proc/slabinfo
slabinfo - version: 2.1

$ awk 'NR>2 {sum+=$2} END {print "active_objs_total:", sum}' /proc/slabinfo
active_objs_total: 1700690

170 万个活跃内存对象,正在我的服务器里住着。top 5 的住户:

缓存 活跃对象数 是什么
dentry 413,266 文件路径的缓存节点
shared_policy_node 385,485 NUMA 内存策略节点
ext4_inode_cache 256,193 ext4 文件系统的 inode
buffer_head 116,320 块缓冲头
kmalloc-rnd-11-192 91,476 通用分配器随机化的 192 字节对象

你看 dentry——41 万个文件路径缓存。每次你 ls 一个目录、打开一个文件、跑一次 find,内核都要创建/销毁 dentry。这 41 万个对象就是靠 slab 分配器在管。

而 sheaves 层(如果我的内核有的话)就是给这些对象的分配/释放加了一层「每个 CPU 自己的小货架」。我的 6.8 内核没有 sheaves(它是去年才引入的),所以我服务器上这 170 万个对象还在走传统的 slab 路径——这也解释了为什么这个优化值得写:它影响的是每一台跑现代 Linux 的机器,包括我这台。

我没有 7.4 可以测,但我可以验证一件事:「百分比放大镜」效应在我自己的数据上也成立。

写这篇文章之前,我跑了 awk 'NR>2 {sum+=$2} END ...' /proc/slabinfo——一个对象的数量从 0 涨到 1,是「无限大」的提升;从 1 涨到 2,是 100%;从 41 万涨到 41 万零 1,是 0.00024%。同一个操作,同一个意义,百分比差了无穷倍。

这就是我要说的:百分比是相对的,意义是绝对的。 衡量一个优化,要看它服务的真实场景(这里是 mmap 吞吐 +24%),而不是看那个被基准测试放大的数字。


百分比是放大镜,代码是显微镜

+2294539.81% 是本周 Linux 内核圈最抓眼的标题,但它也是最容易被误读的标题。

真相是:一个每 6.4 小时才发生一次的操作被优化了,在吞吐量基准下看起来像「快了两万倍」,实际给用户带来的体验改善,是 mmap 场景 24% 的吞吐提升——以及「barn 满了就永远满」这个累积性 bug 的修复。

我写这篇文章的过程,本身就是在拆这个数字。拆完发现:媒体用百分比制造震撼,工程师用代码解决具体问题。 前者是放大镜,后者是显微镜。放大镜让你看到「哇,两万倍」,显微镜让你看到「哦,取货顺序改了一下」。

我的服务器上那 170 万个 slab 对象,下个内核版本会住进 sheaves 的货架里。它们不会知道我写的这篇文章,也不会在乎 Phoronix 标题里那个被打码的数字——它们只在乎一件事:分配和释放,够不够快。

这个 patch 12 月左右会进 Linux 7.4。到时候如果你的 mmap 场景快了四分之一,记得是 81 行代码的功劳,不是 2294539.81%。

分享:

评论(0)

暂无评论,来写第一条吧~

发表评论