多核与 NUMA 架构 —— 从核到多路服务器
第七篇讲清了一个核怎么变快,本篇讲多个核怎么协作。从单芯片多核(CMP)到多路服务器(多 socket),从 Ring Bus 到 Mesh,从 SMP 到 NUMA——多核架构的演进是过去二十年服务器 CPU 最重要的变化。
为什么要多核
2004 年前后 CPU 频率撞上功耗墙:
- 频率每翻倍,动态功耗约翻 8 倍(与频率立方近似成正比)
- Pentium 4 在 3.8 GHz 之后就推不动了
往多核走是必然选择:
1 | |
核数翻倍但每核降频降压,整体功耗持平,并行性能更好。这是 Intel Core 2 Duo(2006)开启多核时代的物理原因。
核间互联拓扑
多核要协作,就需要”芯片内的网络”。主流拓扑三种:
1. 总线 / Crossbar(早期)
graph TB C1[Core 1] --- BUS C2[Core 2] --- BUS C3[Core 3] --- BUS C4[Core 4] --- BUS BUS[共享总线] --- L3 BUS --- MC[内存控制器]
简单,但核数越多冲突越严重。只适合 2-4 核。
2. Ring Bus(环形总线)
Intel 在 Sandy Bridge 到 Broadwell(2011-2015)的 server CPU 用环形总线:
graph LR C1[Core 1] --> C2[Core 2] --> C3[Core 3] --> C4[Core 4] C4 --> L3a[L3 slice] L3a --> MC[MC] MC --> C1
每个核挂一个”站点”,数据在环上传递。两个方向都跑,平均跳数是 N/4。适合 8-16 核,再多就跳数过大、延迟高。
3. Mesh(网格)
Intel 从 Skylake-SP(2017)切换到二维 Mesh:
graph TB C00[Core] --- C01[Core] --- C02[Core] --- C03[Core] C00 --- C10[Core] --- C20[Core] --- C30[Core] C01 --- C11[Core] --- C21[Core] --- C31[Core] C02 --- C12[Core] --- C22[Core] --- C32[Core] C03 --- C13[Core] --- C23[Core] --- C33[Core]
每个 tile 上挂一个核 + 一个 L3 slice + 部分 IO,可双向传递。适合 28-128 核——AMD Chiplet 内 CCD 也是类似思路。
Chiplet 时代的”两级网络”
AMD Zen 2 起把网络切成两级:
graph TB
subgraph CCD0["CCD 0 (8 核)"]
direction LR
C00[Core] --- L30[L3 slice]
end
subgraph CCD1["CCD 1 (8 核)"]
direction LR
C10[Core] --- L31[L3 slice]
end
IOD[IO Die
内存控制器 + Infinity Fabric Switch]
CCD0 -- IF Link --> IOD
CCD1 -- IF Link --> IOD
- CCD 内:核 + L3 共享,延迟很低
- CCD 间:必须经过 IO Die 和 Infinity Fabric
这导致 AMD CPU 内部存在”伪 NUMA“——同一物理 socket 内不同 CCD 之间访问 L3 也有延迟差异。BIOS 里的 NPS 设置(NUMA Per Socket)就是为此而来。
SMP vs NUMA
SMP:对称多处理
graph TB C1[CPU] --- BUS[共享总线] C2[CPU] --- BUS BUS --- MEM[(单一共享内存)]
所有 CPU 访问内存的延迟一致。简单优雅,但扩展性差——总线带宽很快被多核压死。
NUMA:非一致访存
每个 CPU 有自己的”本地内存”,访问本地快,跨节点慢:
graph TB
subgraph N0["NUMA Node 0"]
C0[CPU 0]
M0[(内存 0)]
C0 -- 本地, ~80ns --> M0
end
subgraph N1["NUMA Node 1"]
C1[CPU 1]
M1[(内存 1)]
C1 -- 本地, ~80ns --> M1
end
C0 <-- UPI/IF
~140ns --> C1
| 访问类型 | 典型延迟(DDR5 系统) |
|---|---|
| 本地 NUMA | ~80 ns |
| 跨 NUMA | ~140-200 ns |
| 跨 socket(UPI) | ~140-200 ns |
| 跨 CCD(同 socket) | ~100-130 ns |
NUMA 是”牺牲一致性换扩展性“——多 socket 服务器的必然选择。
NUMA 在多 socket 服务器里的样子
一台双路 EPYC 9754(Bergamo)服务器:
graph TB
subgraph SK0["Socket 0"]
direction TB
M0L[(本地内存 768GB)]
CPU0[128 核]
M0L --- CPU0
end
subgraph SK1["Socket 1"]
direction TB
M1L[(本地内存 768GB)]
CPU1[128 核]
M1L --- CPU1
end
CPU0 <-- "Infinity Fabric
4 链路 / 76.8 GB/s 单向" --> CPU1
如果 BIOS 设 NPS=4(每 socket 切 4 个 NUMA 节点),整机就有 8 个 NUMA 节点。
查看 NUMA 拓扑
Linux 上用 numactl 和 lscpu:
1 | |
numactl --hardware 的关键输出之一是距离矩阵:
1 | |
10 = 本地访问基准,21 = 跨 NUMA 大约慢 2.1 倍。具体数字硬件平台不同。
NUMA 优化:软件视角
应用要适应 NUMA,否则可能跨 NUMA 频繁访问导致严重性能损失。
内存绑定
1 | |
数据库调优
PostgreSQL、MySQL、Redis 都建议:
- 关掉 NUMA balancing,避免频繁迁移内存页
- 用
numactl --interleave=all启动,避免单 NUMA 内存爆炸 - Linux 5.x 起的
vm.zone_reclaim_mode默认值已较合理
大内存 NUMA 大坑
跑过这种代码的人都知道这个坑:
1 | |
正确做法是在每个 NUMA 节点本地申请自己处理的那部分内存——这就是 NUMA-aware 编程。
多 socket 互联:UPI 与 Infinity Fabric
| 厂商 | 互联协议 | 当前规格 | 链路数(双路) |
|---|---|---|---|
| Intel Skylake-Cascade | UPI 1.0 | 10.4 GT/s | 2-3 |
| Intel Ice Lake | UPI 1.0+ | 11.2 GT/s | 2-3 |
| Intel SPR/EMR | UPI 2.0 | 16 GT/s | 4 |
| Intel Granite Rapids | UPI 2.0+ | 24 GT/s | 6 |
| AMD Naples | Infinity Fabric | ~37 GB/s | 4 |
| AMD Rome/Milan | IF | ~64 GB/s | 4 |
| AMD Genoa/Turin | IF Gen 4 | ~76.8 GB/s 单向 | 4 |
待补充:Genoa/Turin 的精确 IF 速率以及 8 路场景的 UPI 拓扑(Sapphire Rapids 8P)。
UPI/IF 链路数越多,跨 socket 的总带宽越高、性能损失越小。双路场景下,4 链路是当前主流。
4 路 / 8 路服务器的 UPI 拓扑
多路服务器的 UPI 拓扑有讲究——不是所有 socket 都直连。
4 路全互联(典型 SPR/EMR)
graph TB CPU0 --- CPU1 CPU0 --- CPU2 CPU0 --- CPU3 CPU1 --- CPU2 CPU1 --- CPU3 CPU2 --- CPU3
每两颗 CPU 都直连,跨 socket 一跳到位。
8 路 Twisted Hypercube(Cooper Lake/Cascade)
1 | |
这种拓扑下”距离矩阵“会出现 10/16/24 三档——跨节点延迟差别可达 2.4 倍。BIOS 里的”snoop modes”调优就是为这个场景准备的。
“巨型机柜”也是 NUMA
NVIDIA GB200 NVL72 整机柜把 72 颗 GPU 放在同一个 NVLink Domain 里,可以”像一颗大 GPU”地编程。但每颗 GPU 之间的 NVLink 距离/延迟仍有差异——这本质上也是 NUMA,只是把概念扩展到了 GPU 之间。
未来 CXL 3.x 把”内存池”概念引入数据中心,会带来”机柜级 NUMA“——一颗 CPU 既能访问本地内存,也能访问机柜里某台机器的远端内存,延迟分级。
一张总结
graph TB L1[单核:流水线/超标量] L2[多核共享 L3:Ring/Mesh] L3[Chiplet 内的"伪 NUMA"] L4[多 socket NUMA:UPI/IF] L5[整机柜 GPU 域:NVLink] L6[CXL 内存池:机柜级 NUMA] L1 --> L2 --> L3 --> L4 --> L5 --> L6
核间通信延迟随层级递增几乎是数量级:
1 | |
每跨一层,应用都需要新的”NUMA-aware”编程方式。这是分布式系统设计的根本约束。
小结
- 多核是 2004 年功耗墙之后的必然选择
- 核间互联从 Ring → Mesh,Chiplet 时代演化为两级网络
- NUMA = 牺牲一致性换扩展性,多 socket 必然 NUMA
- UPI 链路数和 IF 链路数决定了跨 socket 性能
- “NUMA”概念正在从 socket 间扩展到 GPU 间、CXL 池间——本质上是同一个问题
下一篇是本章倒数第二篇——CPU 的命名规则和选型方法。