0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

谷歌在内存方面依赖于per memcg lru lock

Linux阅码场 来源:Linuxer 作者:Linuxer 2021-01-15 14:00 次阅读

电子计算机诞生以来,内存性能一直是行业关心的重点。内存也随着摩尔定律,在大小和速度上一直增长。现在的阿里云服务器动辄单机接近TB的内存大小,加上数以百记的CPU数量也着实考验操作系统的资源管理能力。

作为世间最流行的操作系统Linux, 内核使用LRU, Last Recent Used 链表来管理全部用户使用的内存,用一组链表串联起一个个的内存页,并且使用lru lock来保护链表的完整性。

b3ca29b4-56f1-11eb-8b86-12bb97331649.png

所有应用程序常用操作都会涉及到LRU链表操作,例如,新分配一个页,需要挂在inactive lru 链上, 2次访问同一个文件地址, 会导致这个页从inactive 链表升级到active 链表, 如果内存紧张, 页需要从active 链表降级到inactive 链表, 内存有压力时,页被回收导致被从inactive lru链表移除。不单大量的用户内存使用创建,回收关系到这个链表, 内核在内存大页拆分,页移动,memcg 移动,swapin/swapout, 都要把页移进移出lru 链表。

可以简单计算一下x86服务器上的链表大小:x86最常用的是4k内存页, 4GB 内存会分成1M个页, 如果按常用服务器256GB页来算, 会有超过6千万个页挂在内核lru 链表中。超大超长的内存链表和频繁的lru 操作造成了2个著名的内核内存锁竞争, zone lock, 和 lru lock. 这2个问题也多次在阿里内部造成麻烦, 系统很忙, 但是业务应用并没得到多少cpu时间, 大部分cpu都花在sys上了。一个简单2次读文件的benchmark可以显示这个问题, 它可以造成70%的cpu时间花费在LRU lock上。

作为一个知名内核性能瓶颈, 社区也多次尝试以各种方法解决这个问题, 例如,使用更多的 LRU list, 或者LRU contention 探测。

但是都因为各种原因被linux 内核拒绝。

寻找解决方案

通过仔细的观察发现, 内核在2008年引进内存组-memcg以来, 系统单一的lru lists已经分成了每个内存组一个lru list, 由每个内存组单独管理自己的lru lists。那么按道理lru lock的contention应该有所减小啊?为什么还是经常在内部服务器观察到lru lock hot引起的sys 高?

原来, 内核在引入per memcg lru lists后,并没有使用per memcg lru lock, 还在使用旧的全局lru lock 来管理全部memcg lru lists. 这造成了本来可以自治的memcg A, 却要等待memcg B 释放使用的lru lock。然后A拿起的lru lock又造成 memcg C的等待。。。

那么把全局lru lock拆分到每一个memcg中, 不是可以理所当然的享受到了memcg独立的好处了吗?这样每个memcg 都不会需要等待其他memcg 释放lru lock。锁竞争限制在每个memcg 内部了。

b426b940-56f1-11eb-8b86-12bb97331649.png

要完成lru lock 拆分,首先要知道lru lock 保护了多少对象, 通常情况中, page lru lock需要保护lru list完整性, 这个是必须的。与lru list相关的还有page flags中的lru bit,这个lru bit用作页是否在lru list存在的指示器, 可以避免查表才能知道页是否在list中。那么lru lock保护它也说的通。

但是lru lock 看起来还有一些奇怪的保护对象,承担了一些不属于它的任务:

1.PageMlock bit,保护 munlock_vma 和split_huge_page 冲突,

其实, 上述2个函数在调用链中都需要 page lock, 所以冲突可以完全由page lock来保证互斥。这里lru lock使用属于多余。

2.pagecache xa_lock和memcg->move_lock,

xa_lock并没有需要lru lock保护的场景,这个保护也是多余。相反,lru lock放到xa_lock 之下, 符合xa_lock/lock_page_memcg, 的使用次序。反而可以优化 lru lock 和 memcg move_lock的关系。

3.lru bit in page_idle_get_page, 用在这里是因为担心 page_set_anon_rmap中, mapping 被提前预取访问,造成异常。用memory barrier 方式可以避免这个预取, 所以可以在page idle中撤掉lru lock.

+ WRITE_ONCE(page->mapping, (struct address_space *) anon_vma);

经过这样的修改, lru lock 可以在memory lock 调用层次中降级到最底层。

b46d8b40-56f1-11eb-8b86-12bb97331649.png

这时, lru lock已经非常简化,可以用per memcg lru lock来替换全局的lru lock了吗?还不行,使用per memcg lru lock 有一个根本问题,使用者要保证 page所属的memcg不变,但是页在生命周期中是可能转换memcg的,比如页在memcg之间migration,导致 lru_lock随着memcg变化, 拿到的lru lock是错误的,好消息是memcg 变化也需要先拿到lru lock锁,这样我们可以获得lru lock之后检查这个是不是正确的锁:

b4a700dc-56f1-11eb-8b86-12bb97331649.png

如果不是, 由反复的relock 来保证锁的正确性。bingo! 完美解决!

由此, 这个feature曲折的upstream 之路开始了。。。

最终解决

这个patchset 2019年发出到社区之后, google的 Hugh Dickins 提出, 他和facebook的Konstantin Khlebnikov 同学已经在2011发布了非常类似的patchset,当时没有进主线。不过google内部生产环境中一直在使用。所以现在Hugh Dickins发出来他的upstream版本。关键路径和我的版本是一样的

2个相似patchset的PK, 引起了memcg 维护者Johannes 的注意, Johannes发现在compaction的时候, relock并不能保护某些特定场景:

b55f3e36-56f1-11eb-8b86-12bb97331649.png

所以他建议,也许增加原子的lru bit操作作为 lru_lock 的前提也许可以保护这个场景。Hugh Dickins 则不认为这样会有效,并且坚持他patchset已经在google内部用了9年了。一直安全稳定。。。

Johannes的建议的本质是使用lru bit代替lru lock做page isolation互斥,但是问题的难点在其他地方, 比如在通常的一个swap in 的场景中:

b5bc905e-56f1-11eb-8b86-12bb97331649.png

swap in 的页是先加入lru, 然后charge to memcg, 这样造成页在加入lru 时,并不知道自己会在那个memcg上, 我们也拿不到正确的per memcg lru_lock, 所以上面场景中左侧CPU 即使提前检查PageLRU 也找不到正确的lru lock 来阻止右面cpu的操作, 然并卵。

正确的解决方案, 就是上面第9步移动到第7步前面, 在加入lru前charge to memcg. 并且在取得lru lock之前检查lru bit是否存在, 这样才可以保证我们可以拿到的是正确的memcg 的lru lock。由此提前清除/检查lru bit的方法才会有效。这个memcg charge的上升, 在和Johannes讨论后, Johannes在5.8 完成了代码实现并且和入主线。

在新的代码基础上, 增加了lru bit的原子操作TestClearPageLRU, 把lru bit移出了lru lock的保护,相反用这个bit来做page isolation的互斥条件, 用isolation来保护页在memcg间的移动, 让lru lock只完成它的最基本任务, 保护lru list完整性。至此方案主体完成。lru lock的保护对象也由6个减小到一个。编码实现就很容易了。

b61bc128-56f1-11eb-8b86-12bb97331649.png

测试结果

方案完成后, 上面提到的file readtwice 测试中,多个memcg的情况下,lru lock 竞争造成的sys 从70% 下降了一半,throughput 提高到260%。(80个cpu的神龙机器)

b652d294-56f1-11eb-8b86-12bb97331649.png

Upstream过程

经过漫长4轮的逐行review, 目前这个feature 已经进入了 linus的 5.11 https://github.com/torvalds/linux

第一版patch 发到了社区后, google的skakeel butt立刻提出, google曾经在2011发过一样的patchset来解决 per memcg lru lock 问题。所以,skakeel 要求我们停止自己开发, 基于google的版本来解决这个问题。然后我才发现真的2011年 google Hugh Dickins 和 Facebook Konstantin Khlebnikov 就大约同时提出类似的patchset。, 但是当时引起的关注比较少,也缺乏benchmark来展示补丁的效果, 所以很快被社区遗忘了。不过google内部则一直在维护这组补丁,随他们内核版本升级。

对比google的补丁, 我们的实现共同点都是使用relock来确保page->memcg线性化, 其他实现细节则不尽相同。测试表明我们的patch性能更好一点。于是我基于自己的补丁继续修改并和Johannes讨论方案改进。这也导致了以后每一版都有google同学的反对:我们的测试发现你的patchset 有bug, 请参考google可以工作的版本。并在linux-next上发现一个小bug时达到顶峰:https://lkml.org/lkml/2020/3/6/627 google同学批评我们抄他们的补丁还抄出一堆bug.

b6a804d0-56f1-11eb-8b86-12bb97331649.png

其实这些补丁和Hugh Dickins的补丁毫无关联, 并且在和Johannes的持续讨论中,解决方案的核心:page->memcg的线性化已经进化了几个版本了, 从relock 到 lock_page_memcg, 再到TestClearPageLRU. 和google的补丁是路线上的不同。

面对这样的无端指责,memcg 维护者 Johannes 看不下去, 出来说了一些公道话:我和Alex同学都在尝试和你不同的方案来解决上次提出的compacion冲突问题,而且我记得你当时是觉得这个冲突你无能为力的:

b7422466-56f1-11eb-8b86-12bb97331649.png

之后google同学分享了他们的测试程序,然后在这个话题上沉默了一段时间。

后来memcg charge的问题解决后, 就可以用lru bit来保证page->memcg互斥了。v17 coding很快完成后。intel 的Alexander Duyck, 花了5个星期, 逐行逐字的review整个patchset, 并其基于补丁的改进, 提出了一些后续优化补丁。5个星期的review, 足以让一个feature 错过合适的内核upstream 窗口。但是也增强了社区的信心。

(重大内核的feature 的merge窗口是这样的:大的feature 在进入linus tree之前, 要在linux-next tree 待一段时间, 主要的社区测试如Intel LKP, google syzbot 等等也会在着重测试Linux-next。所以为了保证足够的测试时间, 进入下个版本重要feature 必须在当前版本的rc4之前进入linux-next。而当前版本-rc1通常bug比较多, 所以最佳rebase 版本是 rc2, 错过最佳merge 窗口 rc2-rc4. 意味着需要在等2个月到下一个窗口。并且还要适应新的内核版本的相关修改。)

基于5.9-rc2的 v18 版本完成后, google hugh dickins同学强势归来,主动申请测试和review,根据他的意见v18 做了很多删减和合并,甚至推翻了一些Alexander Duyck要求的修改。patch 数量从32个压缩到20个。Hugh Dickin 逐行review 了整整4个星期。也完美错过了5.10和入窗口。之后v19, Johannes 同学终于回来开始review. Johannes比较快,一个星期就完成了review。现在v20, 几乎每个patch 都有了2个reviewed-by: Hugh/Johannes.

然而, 这次不像以前, 以前 patchset 没有人关心, 这次大家的review兴趣很大,来了就停不住, SUSE的 Vlastimil Babka 同学又过来开始review, 并且提出了一些coding style 和代码解释要求。不过被强势的Hugh Dickins 驳回:

b781fed8-56f1-11eb-8b86-12bb97331649.png

Hugh 的影响力还是很大的, Vlastimil 和其他潜在的reivewer都闭上了嘴。代码终于进了基于5.10-rc 的 linux-next。不过这个驳回也引起一个在5.11提交窗口的麻烦, memory总维护者 Andrew Morthon突然发现Vlastimil Babka 表示过一些异议。所以他问我:是不是舆论还不一致, 还有曾经推给你一个bug, 你解决了吗?

I assume the consensus on this series is 'not yet"?

Hugh再次出来护场:我现在觉得patchset 足够好了, 足够多人review过足够多的版本了, 已经在linux-next 安全运行一个多月了,没有任何功能和性能回退, Vlastimil也已经没有意见了。至于那个bug, Alex有足够的证据表明和这个补丁无关。。。

b7cd8df8-56f1-11eb-8b86-12bb97331649.png

最终这个patchset享受到了Andrew 向 Linus单独推送的待遇。进了5.11。

后记

在 Linux 上游做事情,有很多成就感,也可以保证自己需要的feature,一直在线, 免去了内核升级维护之苦。但也会面临荆棘和险阻, 各种内部不关心的场景都要照顾到, 不能影响其他任何人的feature。所以相比coding, 大量的社区讨论大概是coding的3~5倍时间,主要是反复的代码解释和修改.

在整个upstreaming的过程中特别值得一提的是一些google的同学态度转变, 从一开始的反对,到最后加入我们。从google方面来说, google在内存方面有很多优化都依赖于per memcg lru lock. 这个代码加入内核也解除了他们9年来的代码维护痛苦。

原文标题:memcg lru lock 血泪史

文章出处:【微信公众号:Linuxer】欢迎添加关注!文章转载请注明出处。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11219

    浏览量

    208872
  • 操作系统
    +关注

    关注

    37

    文章

    6727

    浏览量

    123181

原文标题:memcg lru lock 血泪史

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    关于LRU(Least Recently Used)的逻辑实现

    凑巧看到一个有关LRU(Least Recently Used)的逻辑实现,其采用矩阵方式进行实现,看起来颇有意思,但文章中只写方法不说原理,遂来研究下。LRU(Least Recently
    的头像 发表于 11-12 11:47 151次阅读
    关于<b class='flag-5'>LRU</b>(Least Recently Used)的逻辑实现

    鸿蒙Flutter实战:09-现有Flutter项目支持鸿蒙

    │ ├── printer ├── pubspec.lock ├── pubspec.yaml └── yarn.lock plugins 是依赖于原生平台的插件, components 是平台无关
    发表于 10-23 16:36

    隧道定位导航技术主要依赖于哪些原理或技术

    在交通运输领域,隧道作为连接不同区域的重要通道,其内部的安全与效率问题一直备受关注。尤其是在隧道内,由于山体或建筑物的遮挡,卫星信号往往无法直接到达,传统的GPS等卫星导航定位技术在隧道内难以正常工作。因此,隧道定位导航技术的发展显得尤为重要。那么,隧道定位导航技术主要依赖于哪些原理或技术呢?
    的头像 发表于 08-14 11:04 341次阅读

    聚徽-嵌入式工控机是如何散热的

    嵌入式工控机散热主要依赖于以下几种方式:
    的头像 发表于 08-14 09:21 304次阅读

    为什么需要在JTAG LOCK期间实现RAMIN?

    大家好,我想问一下,为什么我们需要在 JTAG LOCK 期间实现 RAMIN(内存初始化)?
    发表于 07-24 06:35

    谷歌智能家庭Home API平台推送,兼容Matter设备并支持自动化家庭场景应用

    谷歌指出,开发人员有望利用此API创建一个标准化的、跨平台且依赖于谷歌智能家居“自动化引擎”的App。消费者只需使用这一款App便能对各类智能家居设备进行管理。
    的头像 发表于 05-17 14:55 566次阅读

    谷歌Chrome浏览器将提供激进的内存节省方案

    谷歌Chrome浏览器自推出以来因过度消耗内存而广受诟病,因此,近日谷歌已上线“内存使用情况”工具,供用户查看当前标签页面所占内存容量(MB
    的头像 发表于 05-09 16:25 472次阅读

    集成芯片引脚如何辨识方向

    集成芯片引脚的方向辨识主要依赖于芯片的设计特点和标记方式。
    的头像 发表于 03-25 14:07 1726次阅读

    集成芯片管脚顺序识别方法

    集成芯片管脚顺序的识别方法主要依赖于芯片的类型和特征。
    的头像 发表于 03-19 18:14 4842次阅读

    谷歌模型怎么用手机打开文件格式

    谷歌在其AI技术中集成了多种工具和功能,用于处理和识别文件格式。具体到手机上打开的文件格式,这主要依赖于谷歌提供的服务和应用。例如,在Gmail中,用户可以直接查看多种格式的文件,包括PDF
    的头像 发表于 02-29 17:38 829次阅读

    数组和链表在内存中的区别 数组和链表的优缺点

    数组和链表在内存中的区别 数组和链表的优缺点  数组和链表是常见的数据结构,用于组织和存储数据。它们在内存中的存储方式以及优缺点方面存在一些显著的差异。本文将详细探讨这些差异以及它们的优缺点。 1.
    的头像 发表于 02-21 11:30 890次阅读

    FX3 GPIF是否依赖于USB?

    我们基于 FX3 的设备 CAN 由电池供电,因此只需要 USB 即可打开电源,直到切换到电池作为电源。 FPGA 与 GPIF 相连。 但是,我们观察到,只要 USB 断开连接,GPIF事务就会失败。 GPIF是否依赖于USB?
    发表于 01-29 08:34

    LPDDR5X来袭!准备迎接内存速度大爆炸!

    如今,智能、互联和带宽密集型应用依赖于超快、低延迟的内存访问,以实现我们日常生活所依赖的一系列功能。那么,什么样的技术能够满足这些要求?答案就是LPDDR5X SDRAM JEDEC标准,它是LPDDR5的可选扩展。
    的头像 发表于 12-18 17:19 2354次阅读
    LPDDR5X来袭!准备迎接<b class='flag-5'>内存</b>速度大爆炸!

    Redis的LRU实现和应用

    在编程中,计数器是一种基本但强大的工具,用于跟踪和管理数据和资源。本文将深入探讨不同类型的计数器的应用,从Redis的LRU(最近最少使用)缓存淘汰算法的实现,到如何在内存受限的环境中有效地使用计数器,再到普通计数器的巧妙应用。
    的头像 发表于 12-15 09:24 568次阅读

    redis的lru原理

    Redis是一种基于内存的键值数据库,它使用了LRU(Least Recently Used)算法来进行缓存的数据淘汰。LRU算法的核心思想是最近最少使用的数据将会在未来也不常用,因此应该优先
    的头像 发表于 12-05 09:56 600次阅读