侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P0-02 · NUMA 拓扑与跨 socket 的代价

PacketForge
2026-08-18 / 0 评论 / 2 阅读 /
对应路线图: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 追求可预测。

NUMA 本地与远程访问代价

实验怎么跑

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 migrationDPDK 牺牲弹性换确定性

面试 3 问

  1. 为什么跨 socket 访问慢 60%? → 走 QPI/UPI 互连而非本地内存控制器:带宽低、延迟高、还要跨 socket 做缓存一致性协议。实验用随机访问直接测出这个差距(numa_test.c:53-62)。
  2. 内核怎么决定把页分到哪个 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)。
  3. 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

评论 (0)

取消