Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

本页目录

内存层次:Cache、带宽、局部性与一致性 / Memory Hierarchies, Bandwidth, Locality, and Coherence ​

📅 创建时间:2026-07-20 🏷️ 标签:#内存层次 #Cache #带宽 #局部性 #一致性 📚 前置知识:[[03-cpu-parallelism]] 📚 相关知识:[[/02-systems-and-performance/02-computer-architecture-and-hardware/01-computer-architecture-basics]] [[/02-systems-and-performance/02-computer-architecture-and-hardware/05-cuda-kernel-and-memory]]


1. 为什么数据移动决定性能 ​

计算单元速度远高于主存。处理器可以在等待一次内存访问期间执行许多算术操作。

典型层次如下:

text
寄存器      最快、最小、线程私有
L1 Cache    很快、每核或局部共享
L2 Cache    较大、延迟更高
L3 Cache    多核共享、容量更大
主存 DRAM   容量大、延迟高
SSD/网络    更慢,但容量或范围更大
1
2
3
4
5
6

从上往下,容量增加,访问速度下降。


2. 时间局部性与空间局部性 ​

时间局部性 ​

最近访问过的数据很可能再次访问。

cpp
for (int k = 0; k < 100; ++k)
    sum += value; // value 被反复使用
1
2

空间局部性 ​

访问某个地址后,很可能继续访问附近地址。

cpp
for (int i = 0; i < n; ++i)
    sum += a[i]; // 连续访问
1
2

Cache 通常以 Cache Line 为单位搬运,例如 64 字节。连续访问能够充分利用一次搬运的数据。


3. 行优先矩阵的访问顺序 ​

C/C++ 二维数组按行连续存储:

cpp
// 友好:连续访问
for (int i = 0; i < n; ++i)
    for (int j = 0; j < n; ++j)
        sum += a[i][j];

// 不友好:跨行跳跃
for (int j = 0; j < n; ++j)
    for (int i = 0; i < n; ++i)
        sum += a[i][j];
1
2
3
4
5
6
7
8
9

两段代码计算量相同,但第二段可能产生更多 Cache Miss。


4. 分块提高数据复用 ​

矩阵乘法直接访问大矩阵时,数据可能在再次使用前已被逐出 Cache。分块把问题切成能放入 Cache 的小块:

text
大矩阵
┌────┬────┬────┐
│块00│块01│块02│
├────┼────┼────┤
│块10│块11│块12│
└────┴────┴────┘
1
2
3
4
5
6

GPU 的 Shared Memory 分块和 CPU 的 Cache Blocking,本质都是主动提高局部性。


5. 延迟与带宽 ​

  • 延迟:一次访问从发出到返回需要多久
  • 带宽:单位时间最多能搬运多少数据

链表随机访问常受延迟限制,向量连续扫描更容易接近带宽上限。

算术强度 ​

text
算术强度 = 浮点运算次数 / 内存传输字节数
1

向量加法读取两个数组并写入一个数组,每个元素只做一次加法,算术强度很低。矩阵乘法可以重复使用数据,算术强度较高。


6. Cache 一致性 ​

多个 CPU 核心可能缓存同一个内存地址。如果一个核心修改数据,其他核心的旧副本必须失效或更新。

一致性协议保证最终看到正确数据,但会产生:

  • Cache Line 在核心间转移
  • 共享写入导致一致性流量
  • 原子变量成为热点
  • 伪共享导致无意义失效

因此“共享内存编程方便”不代表共享数据是免费的。


7. 内存一致性模型 ​

编译器和 CPU 为提高性能可能重排指令。多线程程序不能只根据源代码顺序推断其他线程观察到的顺序。

C++ 使用原子操作和内存序建立线程间关系:

cpp
data = 42;
ready.store(true, std::memory_order_release);

if (ready.load(std::memory_order_acquire)) {
    use(data); // acquire/release 建立可见性关系
}
1
2
3
4
5
6

除非确实在实现底层并发结构,否则优先使用 Mutex、并发容器和成熟库,避免自行组合复杂内存序。


8. 内存优化顺序 ​

  1. 减少不必要的数据和拷贝。
  2. 让访问尽量连续。
  3. 选择紧凑的数据布局。
  4. 通过分块增加复用。
  5. 减少跨线程共享写入。
  6. 检查 NUMA 和设备间传输。
  7. 使用硬件计数器验证 Cache Miss 和带宽。

8. Working Set ​

Working Set 是阶段内频繁访问的数据集合。

它与进程总内存不同。

输入扩大后,Working Set 跨越 Cache 层次会出现性能拐点。

9. Cache Line ​

Cache 按 Line 搬运和一致性管理。

只访问一个字段也可能搬运整条 Line。

连续访问提高空间利用。 随机访问可能浪费大部分字节。

10. Cache Miss ​

常见分类:

  • Compulsory;
  • Capacity;
  • Conflict;
  • Coherence。

分类帮助提出假设,但要结合硬件事件与访问模式。

11. Write Allocate ​

普通 Store 可能先获取 Cache Line 所有权。

覆盖大数组时会产生额外读流量。

Streaming Store 适合特定连续写场景,需要实测。

12. Prefetch ​

硬件预取器擅长连续和固定步长。

依赖指针链和随机访问难以提前发现。

软件预取距离过近无效,过远会污染 Cache。

13. Memory-Level Parallelism ​

处理器可同时维护多个未完成请求。

独立访问能隐藏部分内存延迟。

单条依赖链即使带宽未满,也可能很慢。

14. AoS 与 SoA ​

AoS 让完整对象相邻。 SoA 让同字段连续。

选择依据全部消费者的字段访问和 SIMD 需求。

热冷字段拆分可以减少工作集。

15. TLB ​

TLB 缓存虚拟到物理页转换。

大工作集和随机访问会增加 TLB Miss。

Huge Page 减少转换,但增加碎片与部署复杂度。

16. Page Fault ​

区分:

  • 首次匿名页;
  • 文件映射;
  • Minor Fault;
  • Major Fault;
  • Copy-on-Write;
  • Swap。

预触碰只是移动成本发生时间。

17. NUMA ​

多插槽系统具有本地与远端内存。

text
CPU node 0 -> memory node 0
CPU node 1 -> memory node 1
1
2

首次触碰、线程亲和性和分片影响位置。

18. NUMA 策略 ​

  • 并行初始化;
  • 按节点分片;
  • 每节点内存池;
  • 只读复制;
  • 减少跨节点写;
  • 粗粒度归约;
  • 测量远端流量。

19. False Sharing ​

不同线程写同一 Cache Line 的不同字段,也会产生一致性抖动。

线程局部累加、分片和对齐是候选方案。

平台 Cache Line 大小和收益需要测量。

20. 分配器 ​

大量小分配带来元数据、锁、碎片和 TLB 压力。

候选方案:

  • 批量;
  • Arena;
  • Pool;
  • Buffer 复用;
  • 阶段统一释放;
  • PMR。

优化必须保持构造、析构、对齐和线程安全。

21. GPU 内存层次 ​

GPU 包含:

  • Registers;
  • Shared Memory;
  • L1/L2;
  • Global Memory;
  • Constant/Texture;
  • Host Pinned;
  • Unified Memory。

每层容量、作用域、带宽和同步不同。

22. 合并访问 ​

相邻线程访问相邻地址更容易合并为少量事务。

Stride 和随机 Scatter/Gather 增加事务。

数据布局应和线程索引一起设计。

23. Shared Memory ​

Shared Memory 用于 Block 内复用和协作。

需要控制:

  • 容量;
  • Bank Conflict;
  • 同步;
  • Tile;
  • Occupancy。

24. Unified Memory ​

统一地址不消除迁移。

观察 Page Fault、Migration、Prefetch 和 Oversubscription。

25. 测量 ​

结合:

  • 端到端时间;
  • 字节估算;
  • Cache/TLB 事件;
  • DRAM 带宽;
  • NUMA;
  • Page Fault;
  • 分配 Profile;
  • GPU Timeline。

26. 实验 ​

  1. 工作集扫描;
  2. Stride;
  3. AoS/SoA;
  4. Pointer Chasing;
  5. False Sharing;
  6. NUMA First Touch;
  7. Huge Page;
  8. GPU 合并访问;
  9. Shared Memory;
  10. Unified Memory 迁移。

27. 完成标准 ​

应能解释:

  • 工作集;
  • 字节流量;
  • 延迟与带宽;
  • Cache/TLB;
  • NUMA;
  • False Sharing;
  • 分配;
  • GPU 内存;
  • 测量证据。

核心总结 ​

  • 现代并行计算经常受数据移动而不是计算能力限制。
  • 局部性决定 Cache 的有效程度。
  • 分块是 CPU、GPU 和科学计算中的通用优化思想。
  • 多核共享数据会触发一致性成本。
  • 必须同时分析延迟、带宽和算术强度。

下一篇:[[05-gpu-architecture]]

最后更新于:

Pager
下一篇系统与高性能知识体系

持续记录,持续成长

Copyright © Tidenflow