前面介绍 Linux 的物理内存管理时,曾提及过 NUMA。CPU 对内存的访问受制于**「内存带宽」**的影响,NUMA 系统将 CPU 和内存划分到不同的节点(node) 上,可以让内存的访问更好地利用 memory bandwidth(本质是硬件层面的并行性)。
但是随之而来的弊端是:一个 CPU 所访问的内存不一定和它在同一个 node 上。如果恰好在同一 node(local access),那么访问时延较短:
而如果不在同一 node(remote access),就需要途经 node 之间的 interconnect(比如 Intel 的 QPI links),相比 local access 就会造成更大的 latency:
内存的分配决定了后续的内存访问方式,因此在 NUMA 系统里,需要首先考虑内存从哪里分配比较合适。
内存分配策略
从本 node 上分配的好处是显而易见的,但是如果出现本 node 上内存已经不够,而其他 node 上还有富余内存的情况,是选择在本 node 上启动内存回收,还是适当地从其他 node 分配呢?
对于 per-cpu 的内存,往往只能坚持 node local 的方式,其他情况则可以考虑就近的 node。所谓“就近”,指的是 numa distance(可使用 “numactl --hardware” 命令查看):
一般小型 NUMA 系统(node 数目为 2 的)倾向于从其他 node 分配,而对于大型 NUMA 系统(node 数目大于等于 4 的),由于跨 node 的内存访问的代价更大,则往往宁愿 reclaim。
具体的决策由内核参数 “/proc/sys/vm/zone_reclaim” 控制,其值默认为 0,表示从其他 node/zone 分配。如果该值设为 1,虽然会回收,也尽量选用轻量级的方式,即只回收 unmmaped 的 page cache 页面【注-1】。如果为 2,则可以回收造成开销更大的,需要 writeback 的页面。为 4 的话,那么需要 swap 操作的匿名页面也会被包含。
而在系统 boot-up 的时候,为了减少在同一 node 上的内存访问拥塞,选择了以 round-robin/interleave 的形式在各个 node 上分配内存,等到 init 进程启动后,再切换到默认的 local 模式 。
这在 NUMA 里叫做 memory policy,由于是和内存分配相关,更准确地应该叫做 allocating policy。策略可以针对整个系统进行设置,也可以按 task 级别设置,比如:
numactl --membind=2 dd if=/dev/zero of=/dev/shm/A bs=1M count=1024
此处之所以用 “task” 这个词,是因为它既可以是 process,也可以是 thread。可是一段内存是可能被多个进程共享的(比如动态链接库,或者由同一 elf 启动的进程的 text segment),同一个进程的多个线程之间也可能共享 VMA,那么如果不同的 task 对同一个内存设置了不同的 policy,该如何是好?
这里又有一个 first touch 的原则,即由第一次访问这段内存的 task 的 policy 决定。
内存迁移的影响
内存分配造成的 remote access 只是一方面,前面讲负载均衡的文章说过,到了 NUMA 域这一层,任务可能在不同的 node 之间迁移,这也会导致 cross-node 的内存访问。
load balancer 的脑子里只会想到 CPU 之间的负载差异,它可考虑不到这些,所以我们需要另外一些机制,来减少任务迁移带来的损失。
一种是 Memory follows CPU,即任务迁移后,把它在原先 node 使用的内存也迁移(拷贝)过来,之后对这部分内存的访问就变为 local access 的模式了。
另一种是 CPU follows Memory,即通过定期的扫描,统计一段时间内一个任务对各个 node 上的内存的访问数量,然后将任务置放到其访问最多的那个 node 上去运行。
由于底层硬件架构和应用层 workload 的多样性,实际需要考虑和权衡的因素还有很多,而这一切,都是由一套被称为 automatic NUMA balancing 的框架来完成的(可通过_“/proc/sys/kernel/numa_balancing”_ 来打开或者关闭)。
AutoNUMA 的主要依据是尽量减少 remote access,但随着硬件技术的进步,跨 node 的内存访问延迟已经有了很大的改善,根据这篇论文的研究,remote access 相比 local access 的差异已经从早期的慢 7 倍提升到只慢 30%。
这意味着 remote access 造成的影响在减弱,那么相对地,其他因素的影响就在上升,当然这不是说减少 remote access 就不再重要,而是说它不再是唯一的目标。
小结
操作系统作为硬件的管理者,其所使用的软件算法会不可避免地随着硬件的发展而调整。而 NUMA 系统的设计,让 CPU 和内存这两种最核心的硬件资源有了结构和层次上的区别,这使得 OS 对任务的调度,既要考虑 CPU 之间负载的平衡,又要兼顾对不同 node 的内存访问的影响,就好比心肺联合移植的外科手术一样,注定了它的难度和复杂性。
**注-1:**unmapped 的页面是指没有被进程页表映射的 page frame,这样的页面在 reclaim 时不需要通过 reserve mapping 解除映射关系,回收最为迅速,所以保持一定比例的 unmapped 页面是有必要的,这又引出另一个内核配置参数,即 “/proc/sys/vm/min_unmapped_ratio”(默认为 1%)。
参考:
- 内核文档 Documentation/admin-guide/sysctl/vm.rst
- 内核文档 Documentation/admin-guide/mm/numa_memory_policy.rst