Linux环境下move_pages()函数执行速度异常缓慢问题排查
分析与解决:
move_pages()迁移大内存耗时久但CPU负载低的问题 我来帮你拆解这个场景——用move_pages()迁移几十GB的4K页面,耗时数分钟但CPU几乎 idle,核心矛盾是内存迁移的瓶颈根本不在CPU,而是卡在NUMA节点间的带宽、内存总线,或者函数本身的异步处理机制上。下面分点给你梳理:
可能的原因分析
- NUMA节点间带宽是核心瓶颈:NUMA架构下,跨节点的内存总线(QPI/UPI)带宽远低于单节点的内存带宽(比如双路Xeon的QPI带宽大概10-20GB/s,而单节点DDR4内存带宽能到50GB/s以上)。迁移几十GB数据需要把源节点的内存读出来,再写到目标节点,全程占满跨节点总线,这时候你的进程大部分时间处于不可中断睡眠状态(D状态),等待总线传输完成,自然CPU负载为0。按15GB/s的总线带宽算,30GB数据就要2分钟左右,完全符合你的情况。
move_pages()的同步/异步处理特性:如果你的调用使用了MIGRATE_SYNC(默认同步模式),进程会一直等待所有页面迁移完成才返回,这期间进程不会占用CPU,只是在内核态等待内存传输;如果用了MIGRATE_ASYNC,内核会把迁移请求放到后台队列由内核线程处理,用户态进程直接返回,但你说耗时数分钟,应该是同步模式下进程在等内核完成所有迁移。- 4K小页面的额外开销(但非CPU瓶颈):几十GB的4K页面意味着数百万个页表项,内核需要遍历页表、刷新TLB,但这些操作要么是批量后台处理,要么占比远低于内存传输时间,所以不会拉高CPU负载,只是会额外增加一点总耗时。
诊断方法
- 验证总线带宽瓶颈:
- 用
numastat -p <你的进程PID>查看跨节点迁移的统计,看migrate列的数值变化;同时用perf stat -e uncore_qpi_link_data_total.rd,uncore_qpi_link_data_total.wr(不同CPU架构事件名可能不同)监控总线读写流量,如果接近总线标称带宽,就坐实了是带宽问题。
- 用
- 查看进程状态:用
ps aux | grep <你的进程>,看STAT列是不是D,如果是,说明进程正在等待不可中断的IO(这里就是内存总线传输),CPU自然没负载。 - 测试小批量迁移:先迁移1GB的4K页面,计算耗时,如果1GB用了7-10秒,那几十GB的耗时就是线性放大的,进一步确认是带宽瓶颈。
- 检查内存分布:用
cat /proc/<pid>/numa_maps查看迁移前后的内存节点分布,确认迁移确实在进行,没有卡住或者失败。
解决方案建议
- 改用大页面(最有效):把需要迁移的内存映射成2MB/1GB的HugeTLB页面,同样的数据量下,大页面的数量只有4K页面的1/512或1/262144,页表操作和迁移的开销会大幅降低,而且大页面是连续内存块,总线传输效率更高,能显著缩短迁移时间。你可以通过
/sys/kernel/mm/hugepages/配置大页面,然后用mmap或者posix_memalign申请大页内存。 - 调整
move_pages()的调用参数:- 如果不需要等待迁移完成再做后续操作,改成
MIGRATE_ASYNC模式,用户态进程会立刻返回,迁移工作由内核后台线程完成,你可以通过后续再次调用move_pages()(传入MIGRATE_GET标志)来检查每个页面的迁移状态。 - 尝试
MIGRATE_SYNC_LIGHT模式,它比同步模式轻量,不会等待所有页面迁移完成,而是发起迁移后就返回,适合大内存场景。
- 如果不需要等待迁移完成再做后续操作,改成
- 优化NUMA配置:
- 关闭自动NUMA平衡(如果没用到的话):
echo 0 > /proc/sys/kernel/numa_balancing,避免自动迁移和你的手动迁移互相干扰。 - 如果是虚拟机,确保宿主机给虚拟机分配的vCPU和内存在同一个NUMA节点,减少跨节点访问的开销。
- 关闭自动NUMA平衡(如果没用到的话):
- 并行迁移(谨慎使用):如果你的内存可以分成多个独立的区域,可以用多个线程并行调用
move_pages()迁移不同区域,但要注意不要让跨节点总线过载,否则反而会因为总线竞争导致更慢。
内容的提问来源于stack exchange,提问作者northwindow
相关产品推荐
相关产品推荐

