Skip to content

Cache Line 机制与性能影响详解

Cache Line 机制与性能影响详解(面向 Linux / 驱动 / 高性能开发)

Section titled “Cache Line 机制与性能影响详解(面向 Linux / 驱动 / 高性能开发)”

CPU 访问内存时,并不是按“1字节 / 4字节 / 8字节”逐个读取主存,而是以固定块大小读取数据,这个固定块就叫:

Cache Line(缓存行)

常见大小:

架构常见 Cache Line
x86_6464 Bytes
ARM6464 Bytes(主流)
部分高端平台128 Bytes

Linux 查看方式:

Terminal window
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size

例如输出:

Terminal window
64

表示当前 CPU 的 cache line 为 64 字节。


主存(DRAM)速度远慢于 CPU。

典型延迟(粗略):

访问对象延迟
L1 Cache~1ns
L2 Cache~4ns
L3 Cache~10ns
DRAM60ns120ns

若 CPU 每次只取 8 字节,带宽利用率低。

因此 CPU 会一次读取整条 cache line,例如:

地址 0x1000 ~ 0x103f (64B)

即使程序只访问其中一个 u64,CPU 也会把整条 line 拉入 cache。


现代 CPU 通常:

Core0:
L1D Cache(数据)
L1I Cache(指令)
多个核心共享:
L2 / L3(依架构不同)

示意:

CPU Core
├── L1 Cache(最快)
├── L2 Cache
├── L3 Cache
└── DRAM(最慢)

访问路径越远,延迟越大。


若访问:

arr[0]

CPU 可能顺便加载:

arr[1], arr[2], arr[3]...

因为它们可能在同一 cache line 中。

这就是数组顺序访问快的原因。


4.2 Temporal Locality(时间局部性)

Section titled “4.2 Temporal Locality(时间局部性)”

刚访问过的数据,很快再次访问,大概率仍在 cache 中。

例如:

counter++;
counter++;
counter++;


例如:

struct obj {
u64 a;
char buf[80];
};

总大小 > 64B。

访问该对象时,CPU 可能需要:

line0 + line1

若热点字段跨行:

obj->a
obj->state
obj->ptr

则需要多次 cache fill。


例如:

struct req {
...冷字段...
int state;
void *bio;
};

如果 state/bio 被挤到第二条 line:

原本一次访问完成,变成两次。


例如:

struct item arr[100000];

若:

大小每64B容纳对象
16B4个
32B2个
128B0.5个

对象越大:

  • cache 命中率下降
  • TLB 压力增大
  • 内存带宽压力增大

CPU 访问的数据不在 cache 中:

miss -> 去 L2/L3/DRAM 取

导致 pipeline stall。


CPU 指令准备好了,但等待数据返回。

高并发 IO 路径最怕这个问题。


CPU 会预测连续访问并提前加载。

若对象大小变化异常:

112B stride -> 152B stride

预取效率可能下降。


对象变大后,同样数量对象占更多页。

页表项压力增大,TLB miss 增加。


两个 CPU 修改不同变量,但变量在同一 cache line:

struct stat {
u64 cpu0_cnt;
u64 cpu1_cnt;
};

CPU0 改 cpu0_cnt CPU1 改 cpu1_cnt

虽然变量不同,但同一 line,cache coherency 会频繁失效。

结果:

  • cache ping-pong
  • SMP 性能下降严重

优化:

struct stat {
u64 cpu0_cnt ____cacheline_aligned;
u64 cpu1_cnt ____cacheline_aligned;
};

内核热点结构:

  • task_struct
  • sk_buff
  • bio
  • request
  • page
  • inode

都会考虑:

热字段放前
冷字段后置
减少 padding
减少跨 line
避免 false sharing

原结构体:

112 Bytes

新增字段后:

152 Bytes

若插入中间:

热点字段 offset 改变
跨 cacheline
吞吐下降

若放末尾:

热点字段位置保持不变
性能恢复

说明:

字段位置往往比字段总大小更重要。


struct io_req {
u32 state;
u32 tag;
void *ctx;
char debug[128];
};

struct io_req {
u32 state;
void *ctx;
struct debug_info *dbg;
};

按大小排序:

u64
u64
u32
u16
u8

____cacheline_aligned

Terminal window
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size

Terminal window
pahole vmlinux

查看:

  • offset
  • padding
  • hole
  • member位置

Terminal window
perf stat
perf record
perf report

关注:

  • cache-misses
  • cycles
  • stalled cycles
  • IPC

12. 判断某结构体是否有问题的方法

Section titled “12. 判断某结构体是否有问题的方法”

问自己:

1. 是否每次 IO 都访问?
2. 是否频繁分配释放?
3. 是否多核共享?
4. 是否超过64B?
5. 热字段是否跨line?
6. 是否存在大数组扫描?

若多数为是,则必须优化布局。


结构体不是逻辑组织单位,而是 cacheline 组织单位。


让热点数据尽量停留在同一 cache line;
让不同 CPU 修改的数据分离到不同 cache line;
让冷数据远离 fast path。

若开发:

  • NVMe Target
  • 网络驱动
  • 中断路径
  • Block Layer
  • SCSI
  • 文件系统 fast path

请始终关注:

cache line > 算法复杂度(在热点路径里常成立)

Cache Line 是 CPU 性能核心单位。 结构体布局、对象大小、并发访问模式,都会直接影响:

  • 吞吐量
  • 延迟
  • CPU 使用率
  • 多核扩展性

理解 cache line,是从“会写代码”走向“会做性能优化”的关键一步。