Skip to content

第七十三章:NUMA

元婴后期

涉及内核源码:

林小源在内景中走了很远,走到了一片他从未见过的地貌。

面前是两座巨大的浮岛,悬浮在虚空中,相隔数百丈。每座浮岛上面都有几颗明亮的光球在运转——那是 CPU。浮岛的内部则是一片温暖的金色光芒——那是本地内存。

两座浮岛之间,有一条细细的光桥相连。光桥上偶尔有数据流过,但流速明显比浮岛内部慢得多。

"这是什么地方?"

"NUMA 域。" mm_struct 的声音从左边的浮岛上传来,"Non-Uniform Memory Access。你看到的每座浮岛是一个 NUMA 节点。节点内的 CPU 访问节点内的内存——快。跨节点访问——慢。"

林小源跳上左边的浮岛。脚下的地面温暖而坚实,每一步都能感觉到内存的存在——密集的、均匀的、触手可及。

"这个节点有四个 CPU," mm_struct 说,"本地内存延迟大约 100 纳秒。"

林小源走到浮岛的边缘,望向对面的浮岛。那座浮岛看起来和这边一模一样——四个 CPU,一片金色内存。但两者之间的光桥上,数据流动得缓慢而吃力。

"跨节点访问呢?"

"150 到 300 纳秒。" mm_struct 说,"数据要经过互联总线,从一个节点传到另一个节点。距离越远,延迟越高。"

林小源试着用意念触碰对面浮岛上的内存。他感觉到了——一种遥远的、模糊的、像隔着一层雾的感觉。和触碰本地内存完全不同。本地内存是"近"的、清晰的、立即响应的;远程内存是"远"的、延迟的、需要等待的。

"近和远在内存中也是相对的——他忽然明白了这个道理。"

"不是相对的。" mm_struct 纠正他,"是绝对的。物理距离就是物理距离——数据在导线上传播需要时间。你可以忽略这个距离,但你的程序不会。"

林小源在左边的浮岛上走了一圈,注意到一个奇怪的现象。

有些区域的内存特别"热"——频繁被访问,光芒耀眼。有些区域则冷冰冰的,很少被触碰。而那些热区的位置……似乎在缓慢地移动。

"那些热区是什么?"

"你的工作集。" mm_struct 说,"你频繁访问的内存页面。它们目前都在本地节点上——这是好事。但如果你的进程被调度到了对面的 CPU 上——"

林小源顺着 mm_struct 的指引看向光桥。他明白了——如果他被调度到对面的浮岛,那些热区就变成了远程内存。每次访问都要走光桥,延迟翻倍。

"所以 CPU 和内存的位置关系很重要?"

"非常重要。" mm_struct 说,"这就是为什么有内存策略。"

一个穿着灰色长袍的结构体从浮岛中央走过来。它的身形比 mm_struct 更瘦削,走路的姿态像一个谨慎的管家。

" 。"它自我介绍,声音平稳而精确,"内存策略管理器。我的工作是决定——内存分配在哪个节点上。"

"有哪些策略?"林小源问。

mempolicy 伸出四根手指。

"第一, ——在当前节点分配。最简单,也最常用。你的进程跑在哪个 CPU 上,就在哪个节点分配内存。"

"第二, ——只在指定节点分配。如果那些节点的内存用完了——不回退,直接报错。严格,但可预测。"

"第三, ——在多个节点间轮转分配。第一个页面在节点 0,第二个在节点 1,第三个又回节点 0……像织布一样交错。适合大块内存访问,分散带宽压力。"

"第四, ——优先在指定节点分配。如果那个节点内存不足,可以回退到其他节点。灵活,但不保证。"

林小源想了想。"如果我的进程在两个节点之间频繁迁移呢?"

mempolicy 的眼神暗了一下。"那是最糟糕的情况。你分配的内存可能在一个节点,你运行的 CPU 在另一个节点。每次访问都是远程访问——性能灾难。"

"怎么避免?"

"绑定。" mempolicy 说,"把进程绑定到特定的 CPU,或者设置 策略强制内存在本地分配。自由是有代价的——在 NUMA 世界里,自由意味着你可能不知不觉地访问远程内存。"

林小源坐在浮岛的边缘,双腿悬空。

他闭上眼睛,试着感受两座浮岛之间的"距离"。在意识深处,他"看到"了——数据从本地内存出发,穿过光桥,到达对面的浮岛。每一次穿越都有延迟——不是瞬间的,而是有节奏的,像潮水一样。

"内核有自动 NUMA 平衡。" mm_struct 的声音变得柔和了一些,"它可以检测进程频繁访问的远程内存,自动把页面迁移到本地节点。"

"自动的?"

"自动的。" mm_struct 说,"内核会周期性地扫描进程的页表,标记那些被频繁访问的远程页面。然后在后台把它们迁移到本地节点——对应用程序完全透明。"

林小源睁开眼。他看到光桥上有一些光点在缓慢地移动——不是数据流,而是页面本身在迁移。一个一个地,从一座浮岛飘向另一座浮岛。

"但自动 NUMA 平衡不是万能的。" mm_struct 继续说,"如果进程的工作集太大,本地内存放不下——迁移也没用。如果进程在多个节点间均匀访问——迁移反而会浪费带宽。"

"所以还是要手动设置策略?"

"看你的情况。" mm_struct 说,"如果你知道你的应用的内存访问模式——手动设置策略更好。如果你不确定——让自动 NUMA 平衡来处理,但要监控它的效果。"

林小源站起来,看着两座浮岛之间的光桥。光桥上的光点还在缓慢迁移,像一群萤火虫在夜空中飞舞。

"他喃喃道——距离是性能的关键因素,这不是比喻,是实打实的物理约束。"

"不是因素。" mm_struct 说,"是约束。在 NUMA 世界里,你的每一步都受到物理距离的限制。你可以选择忽略它——但你的程序会为此付出代价。"

林小源点了点头。他跳下浮岛,站在两座浮岛之间的虚空中。脚下是光桥,头顶是无限的虚无。他能同时感受到两座浮岛的温度——近处的温暖,远处的微凉。

在 NUMA 的世界里,"在哪里"和"做什么"一样重要。


c
/*
 * NUMA 架构:
 *
 *   Node 0          Node 1
 *   ┌─────┐         ┌─────┐
 *   │ CPU │         │ CPU │
 *   │ 0,1 │         │ 2,3 │
 *   └──┬──┘         └──┬──┘
 *      │               │
 *   ┌──┴──┐         ┌──┴──┐
 *   │ 本地 │         │ 本地 │
 *   │ 内存 │         │ 内存 │
 *   └─────┘         └─────┘
 *
 * 访问延迟:
 *   本地内存: ~100ns
 *   远程内存: ~150-300ns
 *
 * 内存策略:
 *   MPOL_DEFAULT   — 在当前节点分配
 *   MPOL_BIND      — 只在指定节点分配
 *   MPOL_INTERLEAVE — 在多个节点间轮转分配
 *   MPOL_PREFERRED  — 优先在指定节点分配
 */

printf("=== NUMA — 非一致内存访问 ===\n\n");

printf("NUMA vs UMA:\n");
printf("  UMA (统一内存访问):\n");
printf("    所有 CPU 访问同一内存\n");
printf("    访问延迟相同\n");
printf("    适合小规模系统\n\n");
printf("  NUMA (非一致内存访问):\n");
printf("    每个 CPU 有本地内存\n");
printf("    本地访问快,远程访问慢\n");
printf("    适合大规模系统\n\n");

printf("--- NUMA 节点示例 ---\n");
printf("Node 0: CPU 0-3, 内存 0-16GB\n");
printf("  本地访问: ~100ns\n");
printf("Node 1: CPU 4-7, 内存 16-32GB\n");
printf("  本地访问: ~100ns\n");
printf("  远程访问: ~200ns\n\n");

printf("--- 内存策略 ---\n");
printf("MPOL_DEFAULT:\n");
printf("  在当前节点分配\n");
printf("  最简单的策略\n\n");
printf("MPOL_BIND:\n");
printf("  只在指定节点分配\n");
printf("  如果内存不足,不回退到其他节点\n\n");
printf("MPOL_INTERLEAVE:\n");
printf("  在多个节点间轮转分配\n");
printf("  适合大块内存,分散带宽压力\n\n");
printf("MPOL_PREFERRED:\n");
printf("  优先在指定节点分配\n");
printf("  如果内存不足,回退到其他节点\n\n");

printf("--- set_mempolicy 系统调用 ---\n");
printf("set_mempolicy(MPOL_BIND, &nodemask, maxnode);\n");
printf("  设置当前进程的内存策略\n\n");
printf("mbind(addr, len, MPOL_INTERLEAVE, ...);\n");
printf("  设置特定内存区域的策略\n");

道藏笔记

内核启示

NUMA 架构中,内存访问延迟取决于位置。

NUMA 的特点:

  • 每个 CPU 有本地内存
  • 本地访问快,远程访问慢
  • 适合大规模多处理器系统

内存策略:

  • — 在当前节点分配
  • — 只在指定节点分配
  • — 在多个节点间轮转分配
  • — 优先在指定节点分配

自动 NUMA 平衡:

  • 内核检测远程内存访问
  • 自动迁移页面到本地节点
  • 对应用程序透明

NUMA 让"距离"成为性能的关键因素。


破关试炼

NUMA 之试

本章讲不同内存节点有远近之分,让距离成为性能关键因素的架构叫什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道