# 现代处理器中的存储器层次结构:从寄存器到 DRAM
在现代系统中,缓存未命中耗时 100–200 个周期,而命中只需 1–4 个周期。这决定了代码性能。考虑一个真实案例:在 1 GHz 的 RISC-V 上优化网络接口驱动程序。预期数据包处理速度为每秒 200 万个(每个数据包 500 条指令,500 ns)。实际达到 20 万/秒。
性能分析显示:
$ perf stat -e cycles,instructions,cache-misses ./driver_test
Performance counter stats:
5,000,000 cycles
500,000 instructions
45,000 cache-misses
在 IPC=1 时,50 万条指令需要 50 万个周期,但实际消耗了 500 万个周期。差值是 4.5 万次未命中 × 100 个周期 = 450 万个周期。计算占 10%,内存等待占 90%。
存储器层次结构
存储器以层次结构组织,速度递减,容量递增:
| 级别 | 类型 | 延迟 | 大小 |
|---------|------------|------------|----------|
| 寄存器 | 32 个寄存器 | 1 个周期 | ~128 B |
| L1 缓存 | 指令/数据 | 3–4 个周期 | 32–64 KB |
| L2 缓存 | 统一 | 12–15 个周期 | 256–512 KB |
| L3 缓存 | 共享 | 40–50 个周期 | 2–32 MB |
| DRAM | 主存 | 100–200 个周期 | GB–TB |
关键特性:
- 速度从 1 个周期降至 200 个周期。
- 容量从 128 B 增至 TB。
- DRAM 比 L1 慢 100–200 倍。
在微控制器(RISC-V RV32IMC,100 MHz)中,层次结构简化:
| 级别 | 类型 | 延迟 | 大小 |
|------------|---------------|------------|------------|
| 寄存器 | 32 个寄存器 | 1 个周期 | 128 B |
| I-cache L1 | 指令 | 1 个周期 | 16 KB |
| D-cache L1/SRAM | 数据 | 1–2 个周期 | 8–32 KB |
| Flash | 代码 | ~10 个周期 | 128 KB–1 MB |
| DRAM | 数据 | 50–100 个周期 | 8–64 MB |
差异:缓存较小,无 L2/L3,用 Flash 代替 DRAM 存储代码,容量有限。
缓存行与数据访问
缓存以 64 字节块工作。访问 1 字节会加载整行。
访问 int 示例:
int x = array[0]; // 4 bytes at 0x1000
// Loads 64 bytes: 0x1000–0x103F (16 ints)
顺序访问高效:
for (int i = 0; i < 16; i++) {
sum += array[i]; // 1 miss, 15 hits
}
随机访问则不然:
for (int i = 0; i < 16; i++) {
sum += array[random_index[i]]; // Many misses
}
缓存组织:组与路
缓存分为组和路。直接映射:
地址位:[Tag | Index | Offset]
对于 32 KB、64 B/行:512 行,9 位索引,6 位偏移,17 位标签(32 位地址)。
冲突:数组 a[1024] (0x10000) 和 b[1024] (0x18000) 映射到相同组。
组相联(N 路):一组含 N 行,减少冲突。
典型参数:
- L1:8 路,32–64 KB。
- L2:8–16 路,256–512 KB。
- L3:16 路,2–32 MB。
嵌入式:直接映射或 2 路,冲突概率高。
访问局部性
空间局部性:相邻地址。
良好:
for (int i = 0; i < n; i++) sum += array[i];
不良:
for (int i = 0; i < n; i++) sum += array[random[i]];
时间局部性:重复访问。
良好:
int temp = array[0];
for (int i = 0; i < 1000; i++) result += temp * i;
不良:
for (int i = 0; i < 1000; i++) result += array[i % 10] * i;
最优代码(矩阵乘法):
for (int i = 0; i < N; i++) {
for (int k = 0; k < N; k++) {
int r = A[i][k];
for (int j = 0; j < N; j++) {
C[i][j] += r * B[k][j];
}
}
}
预取器及其局限性
预取器根据模式预加载数据。
- 顺序:for(i) sum += array[i];
- 步长:sum += array[i*2];
局限性:
- 随机访问无效。
- 限于 10–20 行。
- 数组交错会迷惑它。
微控制器中:简单或无。
带宽与多核
DDR4-3200:25.6 GB/s/通道,双通道 — 51.2 GB/s。L1:~1000 GB/s。
嵌入式:SRAM 1–4 GB/s,DRAM 100–500 MB/s。将工作集保持在 SRAM 中。
MESI 协议(x86/ARM):
- M:已修改。
- E:独占。
- S:共享。
- I:无效。
伪共享:
struct { int c0, c1; } shared; // Ping-pong
解决方案:
struct { int c0; char pad[60]; int c1; } shared;
RISC-V:弱内存模型,使用 fence:
sw a0, 0(a1)
fence w, w
sw a2, 0(a3)
原子操作:lr.w/sc.w。
关键要点
- 缓存未命中主导:90% 时间在等待。
- 64 B 行:顺序访问获 15 次免费命中。
- 局部性(空间/时间)——优化关键。
- 嵌入式系统:简化层次,无预取器。
- MESI 和填充防止多核乒乓效应。
— Editorial Team
暂无评论。