对应路线图:Phase 0 (Day 1-21) | 内核 v6.6 | 上一篇:P0-01 cache line | 下一篇:P0-03 PCIe
一句话定位
多路服务器上内存是分片的:访问本 socket 内存约 80ns,跨 socket 走 QPI/UPI 约 130ns。DPDK 犯一次"内存分错了 node"的错,性能直接掉 40%+——所以 EAL 有 --socket-mem、mempool 创建要传 rte_socket_id()。
真实源码精读
实验(source/dpdk-labs/02-numa/numa_test.c)把数组钉在 node 0 上,然后用两种访存模式测延迟:
#define ARRAY_SIZE (32 * 1024 * 1024) // 32M 元素 // numa_test.c:16
#define ITERATIONS 10000000 // numa_test.c:17
// ...
if (numa_available() < 0) {
printf("NUMA not available (VM/单socket环境正常)\n"); // numa_test.c:27-31
return 0;
}
// 在 node 0 上分配内存
long *array = numa_alloc_onnode(sizeof(long) * ARRAY_SIZE, 0); // numa_test.c:37顺序访问(cache friendly,numa_test.c:44-51)大部分命中缓存;随机访问(numa_test.c:53-62)步长 4096(一整页),每次访问必 cache miss,直接暴露 DRAM 真实延迟。这个设计很聪明:同一次实验里同时测到了"缓存的收益"和"内存的底价"。
内核对照(mm/mempolicy.c)——用户态内存策略的三个决策点:
static int policy_node(gfp_t gfp, struct mempolicy *policy, int nd) // mempolicy.c:1868
{
if (policy->mode == MPOL_PREFERRED) {
nd = first_node(policy->nodes); // mempolicy.c:1870-1871
}
// ... 省略 BIND / PREFERRED_MANY 的 home_node 检查 (mempolicy.c:1881-1884)
return nd;
}
/* Do dynamic interleaving for a process */
static unsigned interleave_nodes(struct mempolicy *policy) // mempolicy.c:1889
{
unsigned next;
struct task_struct *me = current;
next = next_node_in(me->il_prev, policy->nodes); // mempolicy.c:1895-1897
if (next < MAX_NUMNODES)
me->il_prev = next;
return next;
}缺页时 vma_alloc_folio() 查 VMA 的策略(mempolicy.c:2166-2186):MPOL_INTERLEAVE 分支用 interleave_nid() 轮转选 node 后调 alloc_page_interleave()(mempolicy.c:2113),最终落到 __alloc_pages(gfp, order, nid, NULL)(mempolicy.c:2118)。"进程级轮流分配"(interleave)与 DPDK 的"一次分配、永久钉死"是两种相反的哲学:内核追求均衡,DPDK 追求可预测。
实验怎么跑
lstopo --no-io # 可视化拓扑(README 02-numa:12)
numactl --hardware # node 距离矩阵(README 02-numa:15)
cd source/dpdk-labs/02-numa
gcc -O2 -o numa_test numa_test.c -lnuma
./numa_test # Sequential vs Random 的 cycles/iter
perf stat -e node-loads,node-load-misses,node-stores,node-store-misses ./numa_test预期:Random 访问的 cycles/iter 比 Sequential 高一个数量级(cache miss → DRAM 延迟);双 socket 机器上 node-load-misses 直接给出跨 node 访问比例。单 socket 环境 numa_available() 会 fallback(numa_test.c:27-31),实验照跑,只是测不出远程延迟——生产调优一定要上真双路机。
🐧 内核对照
| DPDK | 内核 | 关键差异 |
|---|---|---|
rte_socket_id() 传给 mempool/ring 创建(README 02-numa:49-57) | policy_node() 按 mempolicy 选 node(mempolicy.c:1868) | DPDK 显式指定,内核按进程策略 |
--socket-mem 1024,0(README 02-numa:60) | alloc_page_interleave(mempolicy.c:2113) | 内核交错求均衡,DPDK 钉死求局部性 |
| mempool per-lcore cache=256(README 02-numa:65-71) | SLUB per-CPU slab | 同一"先本地后共享"思路 |
| 绑定后无迁移 | 内核自动 page migration | DPDK 牺牲弹性换确定性 |
面试 3 问
- 为什么跨 socket 访问慢 60%? → 走 QPI/UPI 互连而非本地内存控制器:带宽低、延迟高、还要跨 socket 做缓存一致性协议。实验用随机访问直接测出这个差距(numa_test.c:53-62)。
- 内核怎么决定把页分到哪个 node? → VMA 的 mempolicy:
MPOL_PREFERRED时policy_node直接取 first_node(mempolicy.c:1870-1871);MPOL_INTERLEAVE时interleave_nodes按il_prev轮转(mempolicy.c:1895-1897),缺页入口在vma_alloc_folio(mempolicy.c:2166)。 - DPDK 怎么保证内存落在本地 socket? → 创建时传
rte_socket_id()(README 02-numa:49-51),启动时--socket-mem按 node 配额分配 hugepage(README 02-numa:60)。
数据落点
perf-data/P0/02-numa/:numa_test.txt(两种访存 cycles/iter)、perf.node-stat.txt(node-load-misses)、numactl-hardware.txt(拓扑存档)。
评论 (0)