返回首页

CPU 内存层次结构:延迟与优化

内存层次结构章节解释了为什么缓存未命中主导性能。从寄存器到 DRAM 的各级别、64 B 缓存行、访问局部性、预取器和一致性协议 MESI/RVWMO。桌面和嵌入式 RISC-V 系统的示例。

内存延迟:从 1 周期 L1 到 200 DRAM
Advertisement 728x90

# 现代处理器中的存储器层次结构:从寄存器到 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%。

Google AdInline article slot

存储器层次结构

存储器以层次结构组织,速度递减,容量递增:

| 级别 | 类型 | 延迟 | 大小 |

|---------|------------|------------|----------|

Google AdInline article slot

| 寄存器 | 32 个寄存器 | 1 个周期 | ~128 B |

| L1 缓存 | 指令/数据 | 3–4 个周期 | 32–64 KB |

| L2 缓存 | 统一 | 12–15 个周期 | 256–512 KB |

Google AdInline article slot

| 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

Advertisement 728x90

继续阅读