输入关键词开始搜索

CPU 缓存友好编程

缓存层级

L1: 32KB, ~1ns, 每核独享
L2: 256KB, ~4ns, 每核独享
L3: 8-32MB, ~12ns, 核共享
内存: 16GB+, ~100ns

缓存行 (cache line): 64 字节,原子单位
读取 1 字节 → 实际加载整行 64 字节

伪共享 (False Sharing)

// ❌ 两个线程各自修改相邻变量 → 同一缓存行 → 互相失效
struct BadLayout {
    std::atomic<int> counter1;  // 偏移 0
    std::atomic<int> counter2;  // 偏移 4 ← 同一缓存行!
};
// counter1++ 和 counter2++ 会互相使对方的缓存行无效
// → 性能下降 10-100 倍

// ✅ 对齐到不同缓存行
struct alignas(64) GoodLayout {
    std::atomic<int> counter1;
    char pad1[60];              // 填充到 64 字节
};
struct alignas(64) GoodLayout2 {
    std::atomic<int> counter2;
    char pad2[60];
};

数据布局

// ❌ AoS (Array of Structs) — 遍历一个字段时拖入整行无关数据
struct Particle { float x, y, z, vx, vy, vz, mass; };
Particle particles[1000000];

// 遍历所有 x — 每次读 64 字节 = 2.3 个 Particle
// → 大量带宽浪费在不需要的 y, z, vx, vy 上

// ✅ SoA (Struct of Arrays) — 每个字段连续存储
struct Particles {
    std::vector<float> x, y, z, vx, vy, vz, mass;
};
// 遍历所有 x — 每次读 64 字节 = 16 个 float
// → 带宽利用率高 7 倍

分支预测与 Spectre

// 对性能敏感的循环,排序数据可以加速
std::sort(data.begin(), data.end());  // 排序后分支可预测
for (auto x : data) {
    if (x > threshold)  // 连续的真/假 → 分支预测 100% 命中
        doA();
    else
        doB();
}
// 不排序: 分支预测 ~50% 命中 → 流水线刷新 → 慢 3-5 倍

prefetch

#include <xmmintrin.h>

// 提前告知 CPU 将要访问的地址 → 后台加载
for (int i = 0; i < N; ++i) {
    _mm_prefetch((char *)&data[i + 16], _MM_HINT_T0);  // 提前 16 步
    process(data[i]);
}
// 适用于链表遍历等随机访问模式