3What Every Programmer Should Know About Memory

NUMA:非一致性内存访问

多处理器系统的内存架构

NUMA本地内存远程内存第一触及策略

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章 NUMA:非一致性内存访问

多处理器系统的内存架构

章节导读

让我们把时钟拨回 1980 年代。那时的多处理器系统采用一种叫 UMA(Uniform Memory Access,一致性内存访问)的架构:所有 CPU 通过一条共享总线连接到同一块内存。总线的仲裁机制保证每个 CPU 访问内存的延迟相同——听起来很公平,但这条总线很快成了整个系统的瓶颈。想象一条单车道公路连接着一个拥有 32 户人家的小区:无论每户的出行需求多小,所有车辆都必须排队通过这条唯一的道路。CPU 数量翻倍,总线争用不是翻倍而是平方级恶化,4 路以上的 UMA 系统几乎无法扩展。1980 年代的 SGI、Sequent、Encore 等公司给出了激进的替代方案:NUMA(Non-Uniform Memory Access,非一致性内存访问)——把内存分散到各个 CPU 旁边,每个 CPU 访问「自己」的内存很快,访问「别人」的内存要经过互连网络,慢一些但可行。SGI 的 Challenge/Origin 系列是 NUMA 的巅峰之作,Origin 3000 可以扩展到 1024 个处理器,在 1990 年代末的超级计算机领域独领风骚。

架构类型内存访问延迟可扩展性编程复杂度典型规模代表系统
UMA (共享总线)一致 (~100ns)差 (总线瓶颈)低 (透明)1-4 路早期 SMP
NUMA (互连网络)本地 80-100ns / 远程 120-200ns优 (线性扩展)高 (需感知)8-1024 路现代服务器
cc-NUMA (缓存一致)本地 ~80ns / 远程 ~150ns良中 (硬件辅助)8-64 路AMD EPYC
分布式 (无共享)本地 ~80ns / 网络 ~us 级极好高 (显式通信)数千节点集群/MPI

历史绕了一个有趣的圈。2000 年代中期,随着多核处理器的普及,NUMA 以另一种形式回归主流:每个 CPU 插槽(Socket)拥有自己的内存控制器和直连的内存条,插槽之间通过 Intel 的 QPI/UPI 或 AMD 的 Infinity Fabric 互连——这本质上就是一个 2 路、4 路或 8 路的 NUMA 系统。今天你购买的任何一台双路服务器都是 NUMA 机器;AMD EPYC 处理器甚至在单个插槽内部就划分出多个 NUMA 节点(每个 CCD 或每个内存通道组一个节点);ARM 服务器芯片如 Ampere Altra、鲲鹏 920 同样是 NUMA 架构。换句话说,NUMA 不再是超算专属的 exotic 技术,而是每个后端工程师的线上服务器正在运行的现实。Drepper 在 2007 年写这篇论文时,NUMA 编程还属于小众技能;今天,不理解 NUMA 的工程师已经无法解释「为什么同一个服务在两台配置相同的机器上性能差 40%」这类问题——答案往往是进程被调度器迁移到了远程节点,或者内存被分配在了错误的节点上。

NUMA 的性能差异有多大?典型的双路服务器上,访问本地内存的延迟约 80-100 纳秒,访问远程内存约 120-200 纳秒,比值 1.5 到 2.5 倍;带宽差异更悬殊,远程带宽通常只有本地的 50%-70%,因为互连链路比内存总线窄。对于内存密集型应用——数据库、缓存、大数据处理、科学计算——这意味着错误的内存放置可以让性能损失 30%-50%。更麻烦的是,NUMA 问题是「隐形」的:程序逻辑完全正确,功能测试全部通过,只有性能悄悄劣化,而且劣化程度随负载和调度时机波动,极难复现。本章将讲解 NUMA 的硬件原理、Linux 内核的 NUMA 支持(内存策略、第一触及、自动均衡)、numactl 等用户态工具,以及数据库系统的真实优化案例,帮助你建立「NUMA 感知」的编程思维。

核心概念

  • NUMA
  • 本地内存
  • 远程内存
  • 第一触及策略

要点精析

UMA 与 NUMA 的本质区别在于「内存到 CPU 的距离是否均等」。UMA 架构中,所有 CPU 共享一条内存总线(或交叉开关),任何 CPU 访问任何内存地址的延迟和带宽相同,硬件缓存一致性由总线侦听协议维护,编程模型简单——内存就是内存,没有位置概念。但 UMA 的扩展性受限于共享互连:CPU 越多,总线争用越严重,8 路以上基本不可行。NUMA 架构中,内存被划分为多个节点,每个节点直连一组 CPU(通过该组的内存控制器),CPU 访问本节点内存走本地通道(快),访问其他节点内存必须经过节点间互连(慢)。缓存一致性仍然由硬件维护(所以叫 CC-NUMA,Cache-Coherent NUMA),程序不需要显式同步远程数据,但性能强烈依赖数据放置。现代 x86 服务器全部是 NUMA:每个插槽一个或多个节点,插槽间通过 UPI(Intel)或 Infinity Fabric(AMD)互连。

NUMA 的历史见证了「超前技术等待硬件成熟」的经典故事。1980 年代,共享总线多处理器撞上扩展性天花板,学术界和工业界开始探索分布式共享内存架构。1988 年的 BBN TC-2000 和 1991 年的 KSR1 是早期尝试;真正把 NUMA 推向商用巅峰的是 SGI——1993 年的 Challenge 系列和 1996 年的 Origin 2000,后者用 MIPS R10000 处理器和自研的 NUMAlink 互连扩展到 256 路(后续 Origin 3000 达 1024 路),运行 IRIX 操作系统,是当时图形渲染和科学计算的主力平台,电影《泰坦尼克号》的特效就部分渲染于此。与此同时,Sequent(后被 IBM 收购)的 NUMA-Q 在商用数据库领域取得成功。2000 年代中期,x86 阵营跟进:AMD 的 Opteron(K8,2003)首次把内存控制器集成进 CPU,用 HyperTransport 互连实现 x86 NUMA,这是划时代的设计;Intel 在 Nehalem(2008)中以 QPI 应战。从此 NUMA 成为服务器标配,并在多核时代愈发重要——今天连「单路」服务器都可能是 NUMA 的(AMD EPYC 的多芯片粒设计)。

NUMA 节点(Node)是理解一切 NUMA 概念的基本单位。一个节点 = 一组 CPU 核心 + 直连它们的本地内存 + 一个内存控制器。在 Linux 中,节点信息完全暴露在 /sys/devices/system/node/ 目录下:node0/cpulist 列出该节点的 CPU 编号,node0/meminfo 显示该节点的内存总量和空闲量,node0/distance 是一个关键文件——它给出本节点到所有节点的「距离矩阵」,本地距离定义为 10,远程距离通常是 21(同插座跨 CCD)、32(跨插座)甚至更高,数字大致正比于访问延迟。numactl --hardware(或 -H)命令以更友好的方式汇总这些信息:节点数、每节点的 CPU 和内存大小、完整的距离矩阵。理解距离矩阵的实际意义:距离 10 vs 21 意味着约 1.2 倍延迟差,10 vs 32 意味着约 1.5-2 倍延迟差和更大的带宽差。编写 NUMA 感知程序的第一步,永远是用 numactl -H 搞清楚目标机器的拓扑——盲目优化一个不存在的拓扑是常见的错误。

本地与远程内存访问的量化差异是 NUMA 编程的动机所在。以一台典型的双路 Intel Xeon 服务器(每路 16 核,DDR4-3200,UPI 互连)为例:本地内存访问延迟约 85 纳秒,远程约 135 纳秒,比值 1.6;本地单节点读带宽约 130 GB/s(8 通道 DDR4),跨节点远程读带宽约 40-60 GB/s(受 UPI 链路限制,通常 3 条 UPI × 每条约 20 GB/s),比值超过 2。AMD EPYC 7742(单路 64 核,8 个 CCD)在 NPS4 配置下呈现 4 个 NUMA 节点,跨 CCD 访问延迟差异同样在 1.3-1.5 倍。这些数字对应用意味着什么?一个内存带宽受限的流式计算(如大数据扫描),如果一半数据在远程节点,吞吐量可能直接下降 40%;一个延迟敏感的 OLTP 数据库,远程访问比例每增加 10%,P99 延迟可能上升 15%。更隐蔽的是缓存一致性的跨节点流量:多核修改同一数据时,一致性消息也要走 UPI,锁竞争激烈时跨节点锁的代价是本地锁的数倍。

Linux 内核的 NUMA 支持由三部分构成:NUMA 感知的页分配器、进程内存策略(mempolicy)和自动 NUMA 均衡(AutoNUMA)。页分配器为每个节点维护独立的空闲页链表,默认优先从「当前 CPU 所在节点」分配(__GFP_THISNODE 语义的弱化版)。内存策略是每进程(或每 VMA)的属性,决定分配行为,通过 set_mempolicy/mbind 系统调用设置,策略类型包括:MPOL_DEFAULT(本地分配)、MPOL_BIND(严格限制在指定节点,节点内存耗尽则触发 OOM 而非溢出到其他节点)、MPOL_PREFERRED(优先指定节点,耗尽时溢出)、MPOL_INTERLEAVE(轮转分配到多个节点,页粒度打散)。AutoNUMA(3.8 内核引入)则针对「策略无法预知运行时行为」的问题:内核周期性地把部分页标记为不可访问,进程再次访问时触发 NUMA hinting fault,内核据此统计哪个节点在访问哪些页,然后自动把热页迁移到访问它的节点,或把线程迁移到数据所在节点。AutoNUMA 对多数应用透明有效,但对延迟敏感的应用,hinting fault 本身的开销可能有害,可用 /proc/sys/kernel/numa_balancing 关闭。

第一触及(First-Touch)策略是 Linux NUMA 内存分配的默认行为,也是最容易被误解的机制。当你调用 malloc 或 mmap 分配一大块内存时,内核只登记了虚拟地址区域,没有分配任何物理页;物理页的分配发生在第一次写入某个页的时刻(缺页异常),此时内核把页分配在「触发缺页的那个 CPU 所在的节点」上。这带来一个经典的性能陷阱:主线程分配并初始化一个 10GB 的数组(所有页都落在主线程所在节点 0),然后启动 32 个工作线程并行处理——如果一半线程在节点 1 上运行,它们访问的全部是远程内存,带宽和延迟都严重劣化。正确做法是「谁使用,谁初始化」:让每个工作线程自己初始化自己将要处理的那部分数据,这样每个节点的线程都触及本地页。另一个推论:calloc 的零页优化(共享全局零页)会推迟 first-touch 到真正写入时,行为与预期一致;但 MAP_POPULATE 或 mlockall 会在调用线程上下文提前填充所有页,如果调用线程与后续工作线程不在同一节点,反而制造远程内存——需要配合正确的绑定使用。

内存策略的四种模式各有明确的适用场景。MPOL_BIND(绑定):把分配严格限制在指定节点集合,适合「数据必须靠近特定 CPU」的场景,如给绑定在节点 0 的数据库缓冲池分配内存;风险是节点内存耗尽时直接 OOM kill,而不是优雅地溢出到远程节点。MPOL_PREFERRED(优先):优先在指定节点分配,耗尽时自动溢出到其他节点,是 BIND 的安全版本,多数场景应优先使用。MPOL_INTERLEAVE(交错):以 4KB 页为粒度在节点集合上轮转分配,牺牲局部性换取聚合带宽和容量均衡,适合两种场景——单线程程序想利用多节点总带宽(如内存带宽受限的流式计算),以及多线程程序无法预测哪个线程访问哪块数据(如共享哈希表);实测交错策略在带宽受限负载上可比默认策略快 20%-50%。MPOL_LOCAL/DEFAULT(本地):first-touch 语义,适合线程与数据划分明确的并行程序。策略可以在进程级(set_mempolicy,影响后续所有分配)或区域级(mbind,只影响特定 VMA)设置,后者更精细,比如只对缓冲池用 BIND 而对临时内存用 INTERLEAVE。

numactl 是 NUMA 调优的瑞士军刀,无需改一行代码就能控制进程的 CPU 和内存放置。常用形式:numactl --cpunodebind=0 --membind=0 ./app 把进程限制在节点 0 的 CPU 上并只从节点 0 分配内存——这是单实例延迟敏感应用的标准部署方式(代价是放弃一半的 CPU 和内存容量);numactl --interleave=all ./app 让内存交错分布在所有节点——这是 Redis、MongoDB 等「单进程多线程、数据访问模式不可预测」应用的官方推荐配置;numactl --physcpubind=0-7 精确绑定到特定 CPU 编号(跨节点场景配合 taskset 使用);numactl --preferred=1 优先节点 1。numastat 命令则用于观测:numastat -p PID 显示指定进程在每个节点上的内存占用(这是诊断 first-touch 问题的第一工具——如果你发现绑定在节点 1 的线程的内存全在节点 0,就是初始化顺序错了);numastat 不带参数显示系统级的 numa_hit/numa_miss/numa_foreign 计数,numa_miss 持续增长说明分配溢出频繁,节点内存可能不均衡。

多线程程序的 NUMA 优化遵循一套可操作的模式。第一步,线程绑定:用 pthread_setaffinity_np 或 libnuma 的 numa_run_on_node 把每个工作线程固定到一个节点,防止调度器把线程迁移到远离其数据的节点(迁移还会清空该线程的 L1/L2 缓存,双重损失)。第二步,数据分区初始化:按 first-touch 原则,让每个线程初始化(首次写入)自己负责的数据分区,确保页落在本地节点;如果数据由主线程统一分配,可以分配后让各工作线程对自己的分区做一次 memset 触发本地分配。第三步,任务划分对齐拓扑:把计算任务按数据分区划分,线程只处理本地分区;需要跨分区聚合时(如归约),采用树状归约而非全共享累加器,减少跨节点一致性流量。第四步,注意线程池的陷阱:通用线程池(如 OpenMP 默认调度、Java ForkJoinPool)的工作线程可能跑在任意节点,处理任意任务,NUMA 局部性完全靠运气——高性能场景应使用 NUMA 感知的调度器(OpenMP 的 OMP_PLACES/OMP_PROC_BIND 环境变量、TBB 的 NUMA 支持或自研分区线程池)。第五步,测量验证:用 numastat -p 确认内存分布,用跨节点与单节点绑定的 A/B 对比确认收益。

数据库系统是 NUMA 优化的最佳案例库,因为它们是内存密集、多线程、延迟敏感三者的交集。MySQL/InnoDB:官方文档建议用 numactl --interleave=all 启动,因为缓冲池被所有连接线程共享访问,交错分布比集中在单节点更均衡;Percona 的实测显示,在双路服务器上 interleave 相比默认策略可提升 sysbench 吞吐量 10%-20%。PostgreSQL:多进程架构天然适合 NUMA 分区——每个后端进程可以通过 set_mempolicy 绑定到不同节点,社区有按节点划分连接池的实践;shared_buffers 通常建议 interleave。Redis:单线程主循环处理所有键,数据访问不可预测,官方 FAQ 明确推荐 numactl --interleave=all,并提醒 THP 与 fork/COW 的交互问题在 NUMA 上更严重。Oracle:提供专门的 NUMA 参数(_enable_NUMA_support),把 SGA 和 PGA 按实例分区到节点。学术界的标杆是 2014 年的 NUMA-aware 内存数据库论文(如 TUM 的 HyPer 和 Microsoft 的 Hekaton),它们展示了「按 NUMA 节点分区数据 + 分区内单线程执行 + 跨分区用消息传递」的架构可以把多路服务器的扩展效率从 40% 提升到 90% 以上。这些案例的共同教训:NUMA 优化不是微调,而是架构决策。

代码示例

这段代码演示 first-touch 陷阱及其修复。错误版本:主线程分配并 memset 初始化整个数组,然后用 OpenMP 并行求和——所有页都在主线程所在节点,如果 OpenMP 线程分散在两个节点,一半线程访问远程内存。正确版本:主线程只分配(malloc 不触发物理页分配),初始化推迟到并行区域内,由每个线程 memset 自己负责的分区——每个线程的缺页发生在自己运行的节点上,页自动落在本地。用 numastat -p PID 观察两个版本的节点内存分布,错误版本 100% 集中在一个节点,正确版本均匀分布。在双路服务器上实测,正确版本的并行求和带宽可高出 30%-60%。这个模式可以推广为一条军规:在 NUMA 机器上,「分配」和「首次写入」应该在同一个线程、同一个节点上完成;如果做不到,就用显式的内存策略代替 first-touch 的隐式行为。
#include <stdlib.h>
#include <string.h>
#include <omp.h>
#include <stdio.h>
#define N (2UL * 1024 * 1024 * 1024)

int main(void) {
    char *buf = malloc(N);
    #pragma omp parallel
    {
        int tid = omp_get_thread_num();
        int nth = omp_get_num_threads();
        size_t chunk = N / nth;
        memset(buf + tid * chunk, 1, chunk);
    }
    long sum = 0;
    #pragma omp parallel for reduction(+:sum)
    for (long i = 0; i < (long)N; i += 64)
        sum += buf[i];
    printf("sum = %ld\n", sum);
    free(buf);
    return 0;
}
这段代码演示如何用 libnuma API 在程序内实现 NUMA 感知分配,比外部 numactl 更精细。numa_available() 检测运行环境是否为 NUMA(返回 -1 表示内核不支持或非 NUMA 机器,此时应回退到普通 malloc)。numa_alloc_onnode(size, node) 在指定节点上分配内存——它内部就是 mmap 加 mbind(MPOL_BIND),页立即绑定到目标节点,不依赖 first-touch。numa_alloc_interleaved(size) 实现进程内的交错分配,等价于 numactl --interleave=all 但只作用于这一块内存。numa_run_on_node(node) 把当前线程绑定到指定节点的所有 CPU 上(内部调用 sched_setaffinity),numa_node_of_cpu(cpu) 查询某个 CPU 属于哪个节点,numa_max_node() 返回最大节点号。编译需要链接 -lnuma(Debian/Ubuntu 安装 libnuma-dev)。典型用法:为每个工作线程先 numa_run_on_node(n),再 numa_alloc_onnode 分配其私有工作内存,实现「线程-内存-CPU」三位一体的本地化。这是数据库缓冲池、高性能网络框架管理大内存的标准手法。
#include <numa.h>
#include <numaif.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    if (numa_available() < 0) {
        printf("NUMA not available, fallback\n");
        return 1;
    }
    int max_node = numa_max_node();
    printf("nodes: 0..%d\n", max_node);
    for (int n = 0; n <= max_node; n++) {
        size_t sz = 256UL * 1024 * 1024;
        char *p = numa_alloc_onnode(sz, n);
        memset(p, 1, sz);
        printf("allocated 256MB on node %d\n", n);
        numa_free(p, sz);
    }
    return 0;
}
这段代码演示线程与节点的绑定,这是多线程 NUMA 优化的第一步。程序为每个 NUMA 节点创建一个工作线程,用 cpu_set_t 和 pthread_setaffinity_np 把线程钉在该节点的 CPU 集合上,然后每个线程在自己的节点上分配并处理本地数据。关键细节:读取 /sys/devices/system/node/nodeN/cpulist 获得节点的 CPU 列表(格式如 0-7,16-23,需要解析范围和逗号),比硬编码可靠得多;sched_getcpu() 可以在运行时验证线程当前实际运行的 CPU,用于断言绑定生效。绑定带来三重收益:内存访问本地化(配合 first-touch 或 numa_alloc_onnode)、避免调度迁移导致的缓存清空、减少跨节点锁竞争。注意事项:绑定的粒度要匹配负载——CPU 密集的常驻线程适合硬绑定(exclusive 模式),I/O 密集或负载不均的线程绑得太死反而造成节点间负载失衡,此时可以绑定到「节点集合」而非单节点,留给调度器一些自由度。OpenMP 用户可以用环境变量达到同样效果:OMP_PLACES=numa_domains OMP_PROC_BIND=close。
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>

void bind_to_node_cpus(pthread_t t, int node) {
    char path[128], list[256];
    snprintf(path, sizeof(path),
             "/sys/devices/system/node/node%d/cpulist", node);
    FILE *f = fopen(path, "r");
    if (!f) return;
    fgets(list, sizeof(list), f);
    fclose(f);
    cpu_set_t set;
    CPU_ZERO(&set);
    char *tok = strtok(list, ",\n");
    while (tok) {
        int lo, hi;
        if (sscanf(tok, "%d-%d", &lo, &hi) == 2) {
            for (int c = lo; c <= hi; c++) CPU_SET(c, &set);
        } else {
            CPU_SET(atoi(tok), &set);
        }
        tok = strtok(NULL, ",\n");
    }
    pthread_setaffinity_np(t, sizeof(set), &set);
}
这组命令展示 NUMA 诊断的完整工作流,是排查线上 NUMA 问题的标准操作手册。第一步看拓扑:numactl --hardware 输出节点数、每节点 CPU/内存、距离矩阵——先确认机器到底是不是 NUMA、有几个节点。第二步看全局统计:numastat 输出 numa_hit(本地分配成功)、numa_miss(本想本地分配但失败)、numa_foreign(被其他节点借走的分配)、interleave_hit、local_node、other_node 六个计数器,other_node 占比高说明大量分配落到了非运行节点。第三步看进程分布:numastat -p PID 按节点列出该进程的 Huge、Heap、Stack、Private 内存占用,这是诊断 first-touch 问题的决定性证据。第四步做受控实验:分别用 numactl --cpunodebind=0 --membind=0、numactl --interleave=all 和不加 numactl 三种方式启动同一负载,对比吞吐量和 P99 延迟。第五步持续观测:node 级别的 /proc/vmstat 中 numa 计数器的增速,配合 perf stat -e node-loads,node-load-misses 可以量化程序运行时的远程访问比例——node-load-misses 就是跨节点内存访问次数,这个指标应该成为内存密集型服务监控面板上的常驻项。
numactl --hardware
numastat
numastat -p $(pgrep mysqld)
numactl --cpunodebind=0 --membind=0 ./benchmark
numactl --interleave=all ./benchmark
perf stat -e node-loads,node-load-misses ./benchmark
cat /proc/vmstat | grep numa

实验笔记

本章实验需要一个真实的 NUMA 环境:双路服务器最佳;没有的话可以用 QEMU/KVM 模拟 NUMA 拓扑(-numa node,nodeid=0,cpus=0-3,mem=2048 -numa node,nodeid=1,cpus=4-7,mem=2048),或在 AMD EPYC 云主机上开启 NPS 配置,甚至单路 Intel 机器也可以通过内核启动参数构造 fake NUMA。实验一「拓扑侦察」:用 numactl -H、lscpu、/sys/devices/system/node/ 三种途径获取机器的 NUMA 拓扑,画出节点-CPU-内存-距离的示意图,并用一个指针追逐(pointer chasing)基准程序实测各节点对之间的真实访问延迟,验证 distance 矩阵与实际延迟的对应关系。实验二「first-touch 陷阱复现」:实现前面示例中的错误版本和正确版本,用 numastat -p 抓取两个版本的节点内存分布截图,用 perf stat 对比 node-load-misses 数量和总运行时间,量化陷阱的代价。实验三「内存策略对比矩阵」:编写一个内存带宽基准(STREAM Triad 的简化版),在四种配置下运行——默认策略加单节点绑定、默认策略加线程分散、interleave 加线程分散、bind 加线程数据对齐——记录带宽矩阵,解释每种配置的成败原因,这个矩阵会成为你未来做部署决策的直觉基础。实验四「模拟数据库缓冲池」:实现一个多线程的简化键值存储(共享哈希表,8 个工作线程),分别用「全局交错缓冲池」和「按节点分区缓冲池加路由」两种架构实现,压测对比吞吐量和延迟分布,体会论文中 NUMA-aware 数据库架构的优势来源。实验报告要求所有结论都有 numastat/perf 数据支撑,并明确标注测试机器的拓扑参数。

延伸阅读

  • Drepper 原论文第 5 章「NUMA Systems」:本章的原始出处,从硬件互连(当时的 HyperTransport 和 CSI)讲到 Linux 2.6 的 NUMA API,虽然部分细节已过时,但对 first-touch 和内存策略的讲解依然是最清晰的。
  • Linux 内核文档 Documentation/admin-guide/mm/numa_memory_policy.rst:四种内存策略语义的权威定义,set_mempolicy/mbind/get_mempolicy 的行为细节和版本差异都在这里,写 NUMA 代码前必读。
  • numactl 项目主页与手册(github.com/numactl/numactl):numactl、numastat 工具的完整文档和源码,libnuma 的 API 手册(man 3 numa)是编程接口的一手参考。
  • 《Programming Multicore and Many-core Computing Systems》中的 NUMA 章节:从并行编程角度系统讲解 NUMA 感知设计,包含 OpenMP 和 pthread 的拓扑绑定实践。
  • Viktor Leis 等人的论文《The NUMA-Aware Design of Scalable In-Memory Data Structures》以及 TUM HyPer 数据库的系列论文:学术界对 NUMA 感知数据结构(B 树、哈希表、跳表)的最深入研究,展示了分区设计如何把扩展效率提升到接近线性。
  • Brendan Gregg 的博客文章「NUMA Latency」与「Linux NUMA Examples」:包含大量可直接复现的延迟测量命令和真实案例分析,是运维视角的最佳入门材料。
  • AMD EPYC 与 Intel Xeon 的 NUMA 调优白皮书:两家厂商都发布了针对自家多芯片粒/多路架构的官方调优指南,包含 NPS 配置、SNC(Sub-NUMA Clustering)等高级特性的适用场景分析。
3/4NUMA
0%