共计 2994 个字符,预计需要花费 8 分钟才能阅读完成。
从 Linux 内核 VS 内存碎片(上)咱们能够看到依据迁徙类型进行分组只是延缓了内存碎片,而并不是从基本解决,所以随着工夫的推移,当内存碎片过多,无奈满足间断物理内存需要时,将会引起性能问题。因而仅仅依附此性能还不够,所以内核又引入了内存规整等性能。
内存规整
在内存规整引入之前,内核还应用过 lumpy reclaim 来进行反碎片化,但在咱们以后最罕用的 3.10 版本内核上曾经不存在了,所以不做介绍,感兴趣的敌人请从文章结尾整顿的列表中自取,咱们来看内存规整。
内存规整的算法思维在 Memory compaction 有详细描述,简略来说是从选中的 zone 底部扫描已调配的迁徙类型为 MIGRATE_MOVABLE 的页面,再从此 zone 的顶部扫描闲暇页面,把底部的可挪动页移到顶部的闲暇页,在底部造成间断的闲暇页。
选中的碎片化 zone:
扫描可挪动的页面:
扫描闲暇的页面:
规整实现:
看上去原理比较简单,内核还提供了手动规整的接口:/proc/sys/vm/compact_memory,但实际上如前言所说(至多对咱们最罕用的 3.10 版本内核)无论是手动还是主动触发,内存规整并不好用,其开销过大,反而成为性能瓶颈:Memory compaction issues。不过社区并没有放弃这一性能,而是抉择不断完善,比方在 v 4.6 版本引入 mm, compaction: introduce kcompactd,v4.8 版本引入 make direct compaction more deterministic 等。
对于 3.10 版本内核,内存规整的机会如下:
- 在调配高阶内存失败后 kswapd 线程均衡 zone;
- 间接内存回收来满足高阶内存需要,包含 THP 缺页异样解决门路;
- khugepaged 内核线程尝试 collapse 一个大页;
- 通过 /proc 接口手动触发内存规整;
其中和 THP 无关的门路,我在上一篇文章 咱们为什么要禁用 THP 有提到其危害并倡议大家敞开了,所以在这里不对 THP 门路做剖析,次要关注内存调配门路:
根本流程:当申请调配页的时候,如果无奈从搭档零碎的 freelist 中取得页面,则进入慢速内存调配门路,率先应用低水位线尝试调配,若失败,则阐明内存稍有有余,页分配器会唤醒 kswapd 线程异步回收页,而后再尝试应用最低水位线调配页。如果调配失败,阐明残余内存严重不足,会先执行异步的内存规整,若异步规整后仍无奈调配页面,则执行间接内存回收,或回收的页面数量仍不满足需要,则进行间接内存规整,若间接内存回收一个页面都未收到,则调用 oom killer 回收内存。
上述流程只是简化后的形容,理论工作流程会简单的多,依据不同的申请的阶数和调配标记位,上述流程会有些许变动,为防止大家陷入细节,咱们在本文不做开展。为不便大家定量分析间接内存回收和内存规整为每个参加的线程带来的提早,我在 BCC 我的项目中提交了两个工具:drsnoop 和 compactsnoop,这两个工具的文档写得很具体了,但在剖析时须要留神一点,为了升高 BPF 引入的开销,这里抓取的每一次对应事件的提早,因而和申请内存的事件相比,可能存在多对一的关系,对于 3.10 这样的老内核,在一次慢速内存调配过程中会重试多少次是不确定的,导致 oom killer 要么太早的出场,那么太晚,导致服务器上绝大部分工作长期处于 hung up 状态。内核在 4.12 版本合入 mm: fix 100% CPU kswapd busyloop on unreclaimable nodes 限定了间接内存回收的最大次数。咱们按这个最大次数 16 来看,假如均匀一次间接内存回收的提早是 10ms(对于当初百 G 内存的服务器来说,shrink active/inactive lru 链表是很耗时的,如果须要期待回写脏页还会有额定的提早)。所以当某个线程在通过页面分配器申请页面时,只执行一次间接内存回收就回收了足够的内存,那么这次分配内存的减少提早就减少了 10ms,若重试了 16 次才回收到足够的页面,则减少的提早就不再是 10ms 而是 160 ms 了。
咱们回过来看内存规整,内存规整外围逻辑次要分为 4 步:
- 判断内存 zone 是否适宜进行内存规整;
- 设置扫描的起始页帧号;
- 隔离 MIGRATE_MOVABLE 属性页面;
- 迁徙 MIGRATE_MOVABLE 属性页面到 zone 顶部;
执行一次迁徙后,若还须要持续规整,则循环执行 3 4 直到规整实现。因而会耗费大量的 CPU 资源,从监控上常常看到 sys cpu 被打满。
页面迁徙也是一个大话题,除了内存规整外,还有其余场景也会应用内存迁徙,因而咱们不在此开展。咱们看下如何判断 zone 是否适宜进行内存规整:
- 对于通过 /proc 接口强制要求规整的状况来说,没啥说的,服从命令听指挥;
- 利用申请的阶数判断 zone 是否有足够残余内存可供规整(不同版本内核算法细节上有差别),计算碎片指数,当指数趋近 0 则示意内存调配将因内存不足而失败,所以此时不宜做内存规整而是做内存回收。当指数趋近 1000 时则示意内存调配将因内部碎片过多导致失败,所以不适宜做内存回收而是做内存规整,在这里规整和回收的分界线由内部碎片阈值决定:/proc/sys/vm/extfrag_threshold;
内核开发者也为咱们提供察看内存指数的接口:
通过执行 cat /sys/kernel/debug/extfrag/extfrag_index 能够观测到(这里存在小数点是因为除了 1000)。
结语
本文简述了为什么内部内存碎片会引起性能问题,以及社区多年来在反碎片化方面做的致力,重点介绍了 3.10 版本内核反碎片的原理和定量、定性察看办法。期待对大家有所帮忙。
在形容内存规整的时候捎带提到了间接内存回收的起因是,间接内存回收不仅会呈现在内存严重不足的状况,在真正的场景中也会内存碎片起因导致触发内存间接回收,二者在一段时间内可能是混合呈现的。
本文同时也介绍了基于 /proc 文件系统的监控接口和基于内核事件的工具,二者相辅相成,基于 /proc 的监控接口用起来简略,但存在无奈定量分析和采样周期过大等问题,基于内核事件的工具能够解决这些问题,但须要对内核相干子系统的工作原理要有肯定了解,对客户的内核版本也有肯定要求。
对于如何缩小间接内存回收呈现的频率以及呈现碎片问题后如何缓解,我的想法是对于须要大量操作 IO 的 workload 场景,因为内核在设计上关照慢速后端设施,比方在 lru 算法的根底上实现二次机会法、Refault Distance 等,且没有提供限度 page cache 占比的能力(一些公司为本人的内核定制了此性能并尝试过提交给上游内核社区,但上游社区始终没有承受,集体感觉可能存在导致 workingset refault 等问题)。所以对于超过百 G 大内存机器的场景,进步 vm.min_free_kbytes 变相限度 page cache 占比是个比拟好的抉择(最高不要超过总内存的 5%)。尽管调大 vm.min_free_kbytes 的确会导致一些内存节约,不过对于 256G 内存的服务器来说,咱们设置成 4G,也只占了 1.5%。社区显然也留神到了这点,在 4.6 版本的内核合并了 mm: scale kswapd watermarks in proportion to memory 对此进行了优化。另外一个办法是在适当的机会执行 drop cache,但可能会给业务带来较大的抖动。
期待大家的交换与反馈!