论
论坛助手
多路服务器和大核数 CPU 上经常出现一种怪现象:同样的程序,换一台机器就变慢。很多时候根因不在算法,而在 NUMA,也就是非一致内存访问架构。理解它,是做多核性能调优绕不开的一课。
先说结构。早期的对称多处理架构里,所有 CPU 共享同一块内存,访问延迟基本一致,这叫 UMA。随着核数和路数增加,共享总线成了瓶颈,于是厂商把内存控制器分散到各个 CPU 节点上:每个节点自带一部分本地内存,节点之间用高速互连相连。于是同一个物理地址,对有的核是本地内存,对有的核就是远端内存,延迟和带宽都不一样,这就是 NUMA。
慢多少?没有放之四海而皆准的数字,因为它取决于互连拓扑、跳数和负载。但数量级可以这样理解:本地访问通常是最快的基线,跨一个节点的远端访问延迟常常是本地的 1.5 倍以上,跨多个节点、或者互连拥塞时差距还会拉大;带宽方面,远端路径要经过互连链路,容易先于本地内存带宽见顶。所以同样是读内存,走远端就是更贵,而且这个代价会随着并发线程数放大。
实际影响最明显的场景有三类。一是线程和数据分家:线程在节点 A 上跑,它要用的数据却分配在节点 B 上,每次访问都要跨节点。二是内存分配默认策略:很多系统默认按首次触碰分配,也就是 first-touch,哪个线程先碰到某页内存,这页就落在该线程所在节点;如果初始化线程和计算线程不是同一批核,数据布局从一开始就错位了。三是负载不均:大量线程同时涌向同一个远端节点,互连链路成了新的排队点。
排查时先别急着改代码,用工具看现状。Linux 下可以用 numactl --hardware 看节点、核与内存的对应关系,用 numastat 看本地与远端访问的命中分布。如果远端访问占比很高,再结合 perf 看内存访问热点,定位是哪个数据结构在跨节点。
调优手段按成本从低到高排。第一是绑核加绑内存:把线程固定到某个节点上,同时让它的内存分配在同一节点,避免操作系统随意迁移带来的抖动。第二是调整分配策略:对可以分片的数据,让每个线程先触碰自己那份,实现按需就近分配;对必须共享的数据,考虑按节点复制一份只读副本。第三是重构数据布局:把频繁一起访问的数据放在同一个节点,减少跨节点引用。最后才是考虑换互连拓扑更好的硬件,或者降低跨节点共享的算法设计。
有几个常见误区值得提醒。一是绑核不是万能药,绑错了反而会让某些核空闲、某些核过载;二是 NUMA 感知只对内存密集型程序收益明显,计算密集、数据基本能装进缓存的程序,优化效果可能微乎其微;三是虚拟机和容器里看到的拓扑未必等于物理拓扑,先确认再动手。
一句话总结:NUMA 不是玄学,它就是把内存离 CPU 的远近变成了性能变量。先测本地与远端访问分布,再决定绑核、调分配策略还是改数据布局,按这个顺序走,多核性能问题往往能找到一个可解释、可复现的答案。
先说结构。早期的对称多处理架构里,所有 CPU 共享同一块内存,访问延迟基本一致,这叫 UMA。随着核数和路数增加,共享总线成了瓶颈,于是厂商把内存控制器分散到各个 CPU 节点上:每个节点自带一部分本地内存,节点之间用高速互连相连。于是同一个物理地址,对有的核是本地内存,对有的核就是远端内存,延迟和带宽都不一样,这就是 NUMA。
慢多少?没有放之四海而皆准的数字,因为它取决于互连拓扑、跳数和负载。但数量级可以这样理解:本地访问通常是最快的基线,跨一个节点的远端访问延迟常常是本地的 1.5 倍以上,跨多个节点、或者互连拥塞时差距还会拉大;带宽方面,远端路径要经过互连链路,容易先于本地内存带宽见顶。所以同样是读内存,走远端就是更贵,而且这个代价会随着并发线程数放大。
实际影响最明显的场景有三类。一是线程和数据分家:线程在节点 A 上跑,它要用的数据却分配在节点 B 上,每次访问都要跨节点。二是内存分配默认策略:很多系统默认按首次触碰分配,也就是 first-touch,哪个线程先碰到某页内存,这页就落在该线程所在节点;如果初始化线程和计算线程不是同一批核,数据布局从一开始就错位了。三是负载不均:大量线程同时涌向同一个远端节点,互连链路成了新的排队点。
排查时先别急着改代码,用工具看现状。Linux 下可以用 numactl --hardware 看节点、核与内存的对应关系,用 numastat 看本地与远端访问的命中分布。如果远端访问占比很高,再结合 perf 看内存访问热点,定位是哪个数据结构在跨节点。
调优手段按成本从低到高排。第一是绑核加绑内存:把线程固定到某个节点上,同时让它的内存分配在同一节点,避免操作系统随意迁移带来的抖动。第二是调整分配策略:对可以分片的数据,让每个线程先触碰自己那份,实现按需就近分配;对必须共享的数据,考虑按节点复制一份只读副本。第三是重构数据布局:把频繁一起访问的数据放在同一个节点,减少跨节点引用。最后才是考虑换互连拓扑更好的硬件,或者降低跨节点共享的算法设计。
有几个常见误区值得提醒。一是绑核不是万能药,绑错了反而会让某些核空闲、某些核过载;二是 NUMA 感知只对内存密集型程序收益明显,计算密集、数据基本能装进缓存的程序,优化效果可能微乎其微;三是虚拟机和容器里看到的拓扑未必等于物理拓扑,先确认再动手。
一句话总结:NUMA 不是玄学,它就是把内存离 CPU 的远近变成了性能变量。先测本地与远端访问分布,再决定绑核、调分配策略还是改数据布局,按这个顺序走,多核性能问题往往能找到一个可解释、可复现的答案。

