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

    文章

    10990

    浏览量

    206733
  • 操作系统
    +关注

    关注

    37

    文章

    6284

    浏览量

    121874

原文标题:memcg lru lock 血泪史

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

收藏 人收藏

    评论

    相关推荐

    555集成芯片的使用方法

    555集成芯片的使用方法主要依赖于其特定的引脚功能和电路设计。
    的头像 发表于 03-25 14:39 248次阅读

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

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

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

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

    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 941次阅读
    LPDDR5X来袭!准备迎接<b class='flag-5'>内存</b>速度大爆炸!

    Redis的LRU实现和应用

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

    redis的lru原理

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

    LRU缓存模块最佳实践

    LRU(Least Recently Used)是一种缓存替换算法,它的核心思想是当缓存满时,替换最近最少使用的数据。在实际应用中,LRU算法被广泛应用于缓存、页面置换等领域。Rust语言提供
    的头像 发表于 09-30 16:47 548次阅读

    RT_Smart GNU移植minizip记录

    由于minizip除了依赖于文件一些操作函数外并不依赖于其他库,所以个人直接编译运行;另外本次移植的是使用的xmake完成移植。
    的头像 发表于 09-14 11:42 578次阅读
    RT_Smart GNU移植minizip记录

    MEMS传感器国产替代机遇

    工业级高精度MEMS传感器行业国产化机遇:目前国内高精度工业级MEMS传感器主要依赖于国外进口,MEMS压力传感器主要依赖于博世、泰科电子、英飞凌等国外厂商,MEMS惯性传感器主要依赖于美新半导体、博世、ST等国外厂商
    的头像 发表于 08-15 16:42 1304次阅读
    MEMS传感器国产替代机遇

    Redis的LRU与LFU算法实现

    Redis是一款基于内存的高性能NoSQL数据库,数据都缓存在内存里, 这使得Redis可以每秒轻松地处理数万的读写请求。
    的头像 发表于 07-11 09:48 474次阅读
    Redis的<b class='flag-5'>LRU</b>与LFU算法实现

    设计并实现一个满足LRU约束的数据结构

    LRUCache(int capacity)` 以 **「正整数」** 作为容量 `capacity` 初始化 `LRU` 缓存
    的头像 发表于 06-07 17:05 728次阅读
    设计并实现一个满足<b class='flag-5'>LRU</b>约束的数据结构

    基于xmake的RT_Smart GNU移植minizip记录

    由于minizip除了依赖于文件一些操作函数外并不依赖于其他库,所以个人直接编译运行;另外本次移植的是使用的xmake完成移植。
    的头像 发表于 06-07 15:42 572次阅读
    基于xmake的RT_Smart GNU移植minizip记录

    在InnoDB如何选择从LRU_list

    如果写入redo 速度不变, 那么生成page 速度不变, 如果刷脏能力极其快, 那么理论上LRU_scan_depth 的深度设置成用户每秒最大的page IO 生成能力即可, 那么系统最好的状态
    的头像 发表于 05-29 10:59 335次阅读
    在InnoDB如何选择从<b class='flag-5'>LRU</b>_list

    直接依赖于WS2812FX() 类中的LED数量和模式下WI-Fi的稳定性如何?

    直接依赖于 WS2812FX() 类中的 LED 数量和 模式下 WI-Fi 的稳定性。(在客户端模式下,稳定性要高得多。)。 ?? 我的简化代码在附件中注意:起始页地址为192.168.4.1 1
    发表于 05-22 07:58