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

P0-01 · 从 cache line 说起:CPU 缓存怎么坑你

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图:Phase 0 (Day 1-21) | 内核 v6.6 | 上一篇:无(系列第 1 篇)| 下一篇:P0-02 NUMA

一句话定位

CPU 缓存以 64 字节的 cache line 为最小单位在核间保持一致。两个线程各写一个"互不相干"的变量,只要它们落在同一行,就会互相把对方踢出缓存——false sharing。DPDK 所有数据结构(mbuf、ring、per-lcore 变量)的布局纪律,都从这条开始。

真实源码精读

先看实验的结构体定义(source/dpdk-labs/01-cache/false_sharing.c):

/* 两个计数器在同一 cache line */
struct normal {
    volatile uint64_t counter_a;
    volatile uint64_t counter_b;
} __attribute__((aligned(64)));                 // false_sharing.c:20-23

/* 两个计数器被填充隔开,在不同 cache line */
struct padded {
    volatile uint64_t counter_a;
    uint8_t  padding[CACHE_LINE_SIZE - sizeof(uint64_t)];   // 56 字节填充
    volatile uint64_t counter_b;
} __attribute__((aligned(128)));                // false_sharing.c:26-30

两个 worker 各写各的计数器(false_sharing.c:41-53):

void *worker_a_normal(void *arg) {
    (void)arg;
    for (int i = 0; i < ITERATIONS; i++)
        normal_data.counter_a++;
    return NULL;
}
void *worker_b_normal(void *arg) {
    (void)arg;
    for (int i = 0; i < ITERATIONS; i++)
        normal_data.counter_b++;
    return NULL;
}

写 counter_a 会让整行进入 Modified 状态,B 核再写 counter_b 就必须先使 A 的行失效、回内存重取——两个线程在 MESI 协议层乒乓。padded 版本各占一行,互不干扰。源码开头的注释写得很直白(false_sharing.c:5):单线程版 cache_line.c 只是热身,"两个变量在同一线程访问无竞争",多线程版才是真测量。

内核的缓存对齐工具箱(include/linux/cache.h):

#ifndef SMP_CACHE_BYTES
#define SMP_CACHE_BYTES L1_CACHE_BYTES        // cache.h:13
#endif

#ifndef ____cacheline_aligned
#define ____cacheline_aligned __attribute__((__aligned__(SMP_CACHE_BYTES)))  // cache.h:66-68
#endif

#ifndef __cacheline_aligned
#define __cacheline_aligned                    \
  __attribute__((__aligned__(SMP_CACHE_BYTES),            \
         __section__(".data..cacheline_aligned")))    // cache.h:78-82
#endif

内核不仅对齐,还把这些变量放进独立 section(.data..cacheline_aligned),让链接器把它们聚在一起——同样是对齐,内核的做法比实验里手写 aligned() 多一层工程考量。

SLUB 的 per-CPU 设计(mm/slub.c)——内核分配器为什么快:

 * SLUB assigns one slab for allocation to each processor.
 * Allocations only occur from these slabs called cpu slabs.   // slub.c:135-136

每个 CPU 有一个"当前 slab",从上面取对象不需要任何锁(slub.c:117-122 的 lockless fastpath 注释);cpu partial 列表的注释说得更直接(slub.c:4362-4368):局部 partial slab 让再分配"no locking will be required"。这就是 DPDK mempool per-lcore cache 的内核原型。

DPDK 的应用:lib/eal/include/rte_common.h:627 定义 __rte_cache_aligned;lib/mbuf/rte_mbuf_core.h:467 的 struct __rte_cache_aligned rte_mbuf 整个结构体按 cache line 对齐,且第一个 cache line 只放热路径字段(收包时由 descriptor 一次性写入,rte_mbuf_core.h:488 注释 "next 8 bytes are initialised on RX descriptor rearm"),慢路径字段放第二行(rte_mbuf_core.h:606 注释 "second cache line - fields only used in slow path or on TX")。

False sharing 实验与内核对齐工具

实验怎么跑

cd source/dpdk-labs/01-cache
gcc -O2 -o false_sharing false_sharing.c -lpthread
./false_sharing            # 对比 [Same CacheLine] 与 [Padded CacheLine] 的 cycles/op
perf stat -e cache-misses,cache-references,L1-dcache-load-misses ./false_sharing
pahole -C normal false_sharing   # 看结构体真实排布(README 01-cache:29-30)

预期:padded 版本明显更快,perf 里 padded 的 L1-dcache-load-misses 显著低于 normal。注意 README 01-cache 的提醒:单 socket 环境差异可能小,双 socket 差异巨大——这也埋下了 P0-02 NUMA 的伏笔。

🐧 内核对照

DPDK内核关键差异
__rte_cache_aligned(rte_common.h:627)__cacheline_aligned(cache.h:78-82)内核额外放进独立 section
mbuf 双 cache line(rte_mbuf_core.h:467/606)sk_buff 无此纪律DPDK 是手调热路径,内核靠通用性
mempool per-lcore cacheSLUB per-CPU slab + cpu_partial(slub.c:135-136)同一套"先吃本地、再上共享"哲学
RTE_CACHE_GUARD(ring 里隔开 prod/cons)____cacheline_aligned_in_smp(cache.h:70-76)内核宏在 UP 下退化为空

面试 3 问

  1. false sharing 怎么发生、怎么证明? → 两变量同行(false_sharing.c:20-23),MESI 下互相 invalidate;用多线程版对比 cycles/op 和 perf stat 的 cache-misses 证明(false_sharing.c:5 注释解释了为什么单线程版测不出)。
  2. 内核怎么防止结构体成员互相拖累? → ____cacheline_aligned(cache.h:66-68)按 SMP_CACHE_BYTES 对齐;__cacheline_aligned 还放进 .data..cacheline_aligned section(cache.h:78-82)。
  3. 为什么 mbuf 要拆两个 cache line? → 第一个 cache line 由 RX descriptor 一次写入、收包只碰它(rte_mbuf_core.h:488 的 rearm 注释);慢路径字段放第二行(rte_mbuf_core.h:606 注释),不污染热行。

数据落点

perf-data/P0/01-cache/:false_sharing.txt(两次 cycles/op 对比)、perf.stat.txt(cache-misses 实测)。

0

评论 (0)

取消