什么是NUMA架构
简单来说,NUMA(Non-Uniform Memory Access,非统一内存访问架构) 是一种为了提高多处理器系统(多核 CPU)处理效率的内存设计方案。
在传统的架构中,所有处理器访问内存的速度是一样的;而在 NUMA 架构下,处理器访问"离自己近"的内存比访问"远程"内存要快得多。
1. 为什么会出现 NUMA?
在早期的 UMA(Uniform Memory Access) 架构中,所有的 CPU 都通过一根总线连接到同一个内存池。
- 痛点: 随着 CPU 核心数越来越多,大家都挤在这一根总线上抢带宽,就像十个人挤在一个狭窄的厨房里用一个水龙头,效率极低,这被称为"内存墙"问题。
2. NUMA 是如何工作的?
NUMA 将处理器和内存划分为多个节点(Node)。
- 本地访问(Local Access): 每个 CPU 都有自己"私有"的内存控制器和近端内存。CPU 访问这部分内存路径最短,延迟最低。
- 远程访问(Remote Access): 如果 CPU 需要的数据在另一个节点的内存里,它必须通过连接各节点的互联通道(如 Intel 的 QPI/UPI 或 AMD 的 Infinity Fabric)去"借"数据。
- 代价: 远程访问的延迟通常是本地访问的 2 倍甚至更高。
3. NUMA 的优缺点
| 特性 | 描述 |
|---|---|
| 优点 | 极大地扩展了系统的 CPU 核心数量;减轻了总线争用压力;提高了本地内存带宽。 |
| 缺点 | 访问延迟不一致。如果软件没优化(跨 Node 调取数据),性能反而会严重下降。 |
4. 实际应用中的"坑"
如果你在管理服务器(如 Linux 或 数据库),会经常遇到 "NUMA 陷阱":
- 内存分配不均: 有时候 Node 0 的内存用光了,系统开始频繁进行 Swap(交换到磁盘),而 Node 1 还有大量空闲内存。这会导致系统突然变卡。
- 优化策略: * 绑核(Affinity): 让特定的程序只在某个 Node 的 CPU 上运行,并只使用该 Node 的内存。
- 策略调整: 使用
numactl工具来查看节点状态,或通过interleave模式让内存均匀分布。
- 策略调整: 使用
总结
NUMA 就像是把一个大办公室拆成了几个小隔间,每个员工(CPU)桌上都有自己的资料柜(内存)。拿自己柜里的东西很快,去隔壁同事柜里拿东西就得走两步。
怎么判断CPU是UMA 还是 NUMA
简单来说,判断一个 CPU 使用 UMA 还是 NUMA,主要取决于它的核心规模 以及内存控制器的设计。
1. 使用 UMA 架构的 CPU
UMA (Uniform Memory Access) 常见于家用、消费级设备。在这种架构下,所有的核心距离内存的"物理距离"和"逻辑距离"都是相等的。
- 典型代表:
- 绝大多数桌面处理器: 比如 Intel Core i3/i5/i7/i9 系列(如最新的 Ultra 200 系列)和 AMD Ryzen 5/7/9 系列(如 9950X)。
- 笔记本处理器: 几乎所有主流笔记本 CPU。
- 早期服务器: 在多核竞赛开始之前的单路服务器。
- 特点: 结构简单,延迟恒定。虽然 AMD Ryzen 内部有多个 Chiplet(小芯片),但在逻辑层面上,它们通常被抽象为一个统一的内存池,用户感知不到延迟差异。
2. 使用 NUMA 架构的 CPU
NUMA (Non-Uniform Memory Access) 是为了解决多核心、多路服务器的"交通拥堵"而诞生的。
- 典型代表:
- 多路服务器系统: 当一台服务器插了两颗或四颗 Intel Xeon(至强)或 AMD EPYC(霄龙)处理器时,它们天然就是 NUMA 架构。CPU 0 访问插在 CPU 1 旁边的内存条就会变慢。
- 单颗高端服务器处理器: 现在的服务器 CPU 核心太多了(比如 AMD EPYC 的 128 核),为了提高效率,内部会将这 128 核划分为 4 个或更多的 NPS (Nodes Per Socket)。即使只插一颗 CPU,系统里也会显示有多个 NUMA 节点。
- 工作站处理器: 如 Intel Xeon-W 或 AMD Threadripper(线程撕裂者)。
- 特点: 扩展性极强,但需要操作系统和应用(如数据库)具备"NUMA 感知"能力,否则可能导致性能抖动。
3. 对比总结表
| 维度 | UMA (典型桌面机) | NUMA (典型服务器) |
|---|---|---|
| 核心数量 | 较少(通常 4-24 核) | 极多(32-256 核以上) |
| 内存控制器 | 1 个统一的控制器 | 多个分布式的控制器 |
| 访问延迟 | 无论访问哪个地址,延迟一致 | 访问本地快,访问远程慢 |
| 典型应用 | 游戏、日常办公、视频剪辑 | 数据中心、虚拟化、科学计算 |
4. 快速查看
shell
--uma
user@ubuntu:~$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9
node 0 size: 15703 MB
node 0 free: 14280 MB
node distances:
node 0
0: 10
shell
--numa
x@k08:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
node 0 size: 32139 MB
node 0 free: 2525 MB
node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31
node 1 size: 32199 MB
node 1 free: 3565 MB
node distances:
node 0 1
0: 10 20
1: 20 10
First-Touch 策略
可以说,First-Touch(首次触摸) 是现代操作系统处理 NUMA 架构内存分配时最核心、最基础的策略。如果没有 First-Touch,NUMA 架构的性能优势可能会大打折扣。
1. 什么是 First-Touch 策略?
在操作系统中,当你使用 malloc 等函数申请内存时,系统其实只是给你发了一张"空头支票"(虚拟地址),并没有真正把物理内存分配给你。
真正的分配发生在: 某个 CPU 核心第一次 往这段内存里写数据(即"首次触摸")触发缺页中断时。此时,操作系统会观察是哪个核心在操作,然后优先将物理内存分配在离这个核心最近的 NUMA 节点上。
2. 为什么 First-Touch 对 NUMA 至关重要?
它的核心目的是实现内存本地化(Data Locality)。
- 如果没有 First-Touch: 如果系统简单地按顺序分配(比如全塞给 Node 0),那么当运行在 Node 1 上的线程去访问这些数据时,就必须跨越互联总线,产生巨大的延迟(Remote Access)。
- 有了 First-Touch: 谁先用,谁就近持有。这保证了在多线程并行计算中,每个 CPU 核心处理的数据大概率就在它旁边的内存条里。
3. First-Touch 带来的经典"坑":初始化陷阱
这是开发者最容易踩的 NUMA 性能坑。
场景描述:
- 你写了一个高性能程序,准备用 64 个核心处理 64GB 数据。
- 错误做法: 你在程序刚启动时,用主线程 (通常跑在 Node 0)写了一个
for循环把这 64GB 数组全部初始化为 0。 - 结果: 根据 First-Touch 原则,这 64GB 物理内存全都会被分配到 Node 0。
- 后果: 当你后面开启 64 个线程并行计算时,跑在其他 Node 上的线程全都要跨节点去 Node 0 抢数据。你的并行程序瞬间退化成单路带宽,性能崩塌。
4. 如何正确应对?
为了配合 First-Touch,开发者通常采用以下策略:
- 并行初始化: 不要只用主线程初始化数据。用多线程并行初始化,让每个线程负责初始化它之后要处理的那部分内存。这样数据就会均匀分布在各个 NUMA 节点。
- 强制策略: 如果无法预测谁先触摸,可以使用
numactl --interleave=all。这会无视 First-Touch,强行把内存像洗牌一样均匀散布在所有节点上,虽然牺牲了极致的本地速度,但避免了单节点过载。
总结
First-Touch 是 NUMA 性能优化的"灵魂"。 它是操作系统为了让数据离 CPU 更近而做出的智能选择。理解了它,你就理解了为什么在多路服务器上,程序的"启动方式"和"计算方式"同样重要。
uma架构可以用 numactl --interleave=all 吗
简单直接地回答:可以运行命令,但没有任何实际意义。
在 UMA 架构的机器上执行 numactl --interleave=all,就像是在只有一个隔间的办公室里推行"工位轮换制"------无论你怎么换,其实还是坐在同一个房间里。
1. 为什么"没用"?
numactl 是专门为 多节点(Multi-Node) 硬件设计的工具。
- UMA 架构: 系统只有一个 NUMA Node(通常是 Node 0)。当你设置
interleave=all时,系统尝试在所有节点间交叉分配内存,但由于只有一个节点,它只能把所有内存继续丢给 Node 0。 - 效果: 程序的内存分配行为与默认状态完全一致,既不会提升性能,也不会降低性能。
2. 执行后会发生什么?
如果你在普通的笔记本或台式机(UMA)上运行,通常会看到类似这样的反馈:
- 查看状态: 运行
numastat或numactl --hardware,你会发现只有一个node 0。 - 强制执行: 运行
numactl --interleave=all ./your_program,程序会正常启动,不会报错。 - 系统内核处理: Linux 内核会识别到当前系统只有一个内存节点,因此自动忽略所谓的"交错(Interleave)"逻辑,直接退化为标准的本地分配。
3. 什么情况下 UMA 用户会接触到这个?
虽然在物理 UMA 机器上没用,但在以下两种特殊场景中,了解这个命令是有价值的:
- 虚拟机/容器: 有时候你在云服务器(虚拟出来的 UMA 环境)上运行数据库(如 MongoDB 或 MySQL),这些软件的启动脚本通常自带
numactl --interleave=all以防止在真正的 NUMA 物理机上出现严重的性能偏斜(即"NUMA 陷阱")。在这种情况下,保留这个参数是为了兼容性,防止程序迁移到多路服务器时崩溃。 - 开发测试: 开发者在自己的 UMA 电脑上测试脚本,确保在部署到昂贵的 NUMA 服务器之前,逻辑参数设置是正确的。
4. 总结对比
| 架构类型 | interleave=all 的效果 | 实际收益 |
|---|---|---|
| UMA (普通电脑) | 无视指令,维持现状 | 无 |
| NUMA (多路服务器) | 内存平均分布在所有物理 CPU 节点 | 高(防止单节点内存溢出/Swap) |
NUMA 架构的物理布局 与逻辑抽象之间的关系
简单直接的回答是:从物理位置上看,它们通常是不相同的;但从逻辑编号和主板插槽的设计规整度上看,它们是对称的。
为了彻底理解,我们需要从物理插槽和系统识别两个维度拆解:
1. 物理位置:各守一方
在 NUMA 架构(尤其是多路服务器)中,内存条是直接连接在特定 CPU 的内存控制器上的。
- 物理分离: 主板上通常会有明确的区域划分。比如双路服务器,左边的 8 个插槽属于 CPU 0,右边的 8 个插槽属于 CPU 1。
- 独立走线: CPU 0 访问自己旁边的内存条(Local Memory)走的是直接电路;访问 CPU 1 旁边的内存条(Remote Memory)必须经过 CPU 之间的互联总线(如 Intel UPI 或 AMD Infinity Fabric)。
2. 插槽编号:镜像对称
虽然物理位置不同,但为了方便安装和识别,主板厂商通常会采用镜像对称的设计:
- 命名规则: * CPU 0 对应的槽位可能叫
NODE0_DIMM_A1,NODE0_DIMM_B1...- CPU 1 对应的槽位对应叫
NODE1_DIMM_A1,NODE1_DIMM_B1...
- CPU 1 对应的槽位对应叫
- 安装要求: 为了性能平衡,通常要求两个 CPU 对应的内存插法必须一模一样。例如,如果你给 CPU 0 插了 4 条 32GB 内存,那么 CPU 1 也应该在对应的对称位置插上 4 条 32GB 内存。
3. 系统视角:逻辑上的"位置"
在操作系统层面(通过 numactl --hardware 查看),你会发现内存被抽象成了不同的 Node。
- Node 0:包含了 CPU 0 以及与之物理直连的所有内存空间。
- Node 1:包含了 CPU 1 以及与之物理直连的所有内存空间。
关键点: 这里的"位置"在 CPU 看来是有"远近"之分的。
- Local (本地) :延迟约 80-100ns。
- Remote (远程) :延迟可能达到 140-200ns(因为多了跨越 CPU 间总线的开销)。
4. 极端情况:非对称配置
如果我在 CPU 0 旁边插了内存,但 CPU 1 旁边没插内存会怎样?
这就是所谓的"非对称 NUMA":
- CPU 1 变成了"无内存节点": 它所有的内存访问都变成了"远程访问"。
- 性能剧降: 跑在 CPU 1 上的任务会因为等待数据传输而变得非常缓慢。
- 甚至无法开机: 某些服务器主板在检测到内存分布极度不均时,会直接报错或拒绝启动。
总结
不同 CPU 对应的内存条,在主板上的物理坐标肯定不同 (它们分别围在各自 CPU 的身边),但在逻辑架构和安装逻辑上,它们是处于对等地位的镜像关系。
Node Distance
这段输出是 numactl --hardware 命令的标准结果。Node Distance(节点距离) 是一个逻辑数值,用来衡量 CPU 访问不同内存节点时的相对延迟(Latency)。
简单来说,它告诉操作系统:从 CPU A 到内存 B 到底要跑"多远"。
shell
x@k08:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
node 0 size: 32139 MB
node 0 free: 2525 MB
node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31
node 1 size: 32199 MB
node 1 free: 3565 MB
node distances:
node 0 1
0: 10 20
1: 20 10
1. 核心数值解读
在 NUMA 架构中,数值 10 是一个基准(通常代表本地访问)。
- 10 (Local): 代表 CPU 访问直接插在自己旁边的内存。这是速度最快、延迟最低的状态。
- 20 (Remote): 代表 CPU 跨越了总线(如 Intel UPI 或 AMD Infinity Fabric)去访问另一个 CPU 旁边 的内存。
- 含义: 数值 20 意味着访问远程内存的开销大约是本地访问的 2 倍 (\(20/10 = 2\))。
2. 你的数据表怎么看?
这个矩阵需要交叉对比:
| 访问 Node 0 内存 | 访问 Node 1 内存 | |
|---|---|---|
| Node 0 的 CPU | 10 (本地,快) | 20 (远程,慢) |
| Node 1 的 CPU | 20 (远程,慢) | 10 (本地,快) |
3. 为什么这个数值很重要?
操作系统(如 Linux)的调度器会参考这个距离表来做决策:
- 就近分配: 当 Node 0 上的进程申请内存时,内核看到 Node 0 到 Node 0 的距离是 10,到 Node 1 是 20,它会拼命尝试在 Node 0 分配内存。
- 性能预警: 如果你的程序距离显示是 21 或更高(在某些四路服务器上可能出现 10, 21, 31),说明物理路径经过了多次跳转(Hop),延迟会进一步剧增。
4. 你的配置观察
从你提供的数据看,这是一个非常典型的双路服务器(Dual Socket) 或**单路高核数(如 AMD EPYC 分成两个 NUMA 节点)**的配置:
- CPU 映射: 偶数核(0, 2, 4...)在 Node 0,奇数核(1, 3, 5...)在 Node 1。这说明系统物理上是对称分布的。
- 内存分布: 两个 Node 各有约 32GB 内存。
- 健康状态: Node 0 的剩余内存(2525 MB)比 Node 1(3565 MB)少,说明目前跑在 Node 0 上的进程更多。
警惕: 如果 Node 0 的
free变成 0,而 Node 1 还有很多空闲,Node 0 上的进程可能会开始触发 Swap(磁盘交换),即使系统总内存还够用。这就是我们之前聊到的"NUMA 陷阱"。
总结
Node Distance 是衡量"跨界拿数据"代价的标尺。 距离 10 是家门口,距离 20 是隔壁村。
NUMA与Ring Bus(环形总线)
简单来说:Ring Bus(环形总线)是 CPU 内部核心之间的"交通工具" ,而 NUMA 是多个 CPU 核心组之间的"行政区划"。
在现代 CPU 中,它们通常是微观 与宏观的关系。
1. Ring Bus(环形总线):微观的连接方式
Ring Bus 是 Intel 长期使用的一种内部拓扑结构。想象一个环形地铁线,所有的 CPU 核心(Core)、三级缓存(L3 Cache)和系统代理(System Agent)都像车站一样分布在这个环上。
- 工作方式: 数据在环上单向或双向流动。如果核心 1 想要数据,它必须等待数据包"转"到它面前。
- 局限性: 随着核心数增加,环会变得越来越长,数据绕一圈的时间(延迟)也会线性增加。
- 与 UMA 的关系: 在传统的 4 核或 8 核桌面 CPU 中,由于环很短,所有核心访问内存的延迟几乎一样,所以表现为 UMA 架构。
2. NUMA 与 Ring Bus 的结合
当核心数量多到 Ring Bus 跑不动时(例如超过 12-16 个核),芯片设计者会采取两种方案,这便引出了 NUMA:
方案 A:多个 Ring 合并(Cluster on Die)
在一颗高核数处理器内部,设计者可能放入两个 Ring Bus(例如左边 12 核一个环,右边 12 核一个环)。
- 每个环有自己的内存控制器。
- 结果: 虽然是在同一个物理芯片里,但逻辑上变成了两个 NUMA Node。核心访问自己环内的内存很快,跨越到另一个环则慢。
方案 B:放弃 Ring Bus 改用 Mesh(网格)
在极高核数的服务器芯片(如 Intel Xeon Scalable)中,Ring Bus 被 Mesh Fabric(网格架构)取代。网格架构天生为了支持大规模 NUMA 节点设计,让成百上千个核心能以更均衡的延迟通信。
3. 核心区别对比
| 维度 | Ring Bus (环形总线) | NUMA (非统一内存访问) |
|---|---|---|
| 所属层面 | 底层物理拓扑(硬件怎么连) | 系统逻辑架构(系统怎么看) |
| 关注点 | 核心与核心、核心与缓存的通信 | 核心与内存之间的访问成本 |
| 扩展性 | 较差(核心多了延迟太高) | 极强(支持数百核心并行) |
| 关系 | 它是实现单节点内 UMA 的手段 | 它是跨越多个总线后的必然结果 |
4. 形象的比喻
- Ring Bus 就像是一栋大楼里的电梯。 核心是房间,数据是乘客。只要楼层不高,大家坐电梯去哪都很快(UMA)。
- NUMA 就像是多栋大楼组成的园区。 每栋楼内部都有自己的电梯(Ring Bus)。如果你在 A 楼办公,去 A 楼的仓库拿东西很快;但如果要通过连廊去 B 楼拿东西,那就得走更远的路(Node Distance 20)。
5. 现状:为什么 Ring Bus 越来越少见于服务器?
因为 AMD 的 Chiplet(小芯片)设计 彻底拥抱了 NUMA。AMD 的 EPYC 处理器内部没有大环,而是由多个小模块(CCD)组成,每个模块通过 Infinity Fabric 互联。这种结构虽然增加了复杂性,但能轻松堆叠出 128 核甚至更多。