CPU的缓存越来越大,但单端口SRAM的访问速度跟不上处理器的发射宽度。多缓存组(Multi-Banking)技术把一个大缓存拆成多个独立的小块,让它们并行工作。
1. 单端口缓存的瓶颈
现代超标量处理器每周期可以发射多条内存指令。比如Intel Core i7每周期能发射2个load和1个store。但如果缓存是单端口的,这些请求必须排队串行处理,处理器只能干等。
更糟的是功耗。单端口缓存每次访问都要激活整个存储阵列,哪怕只读4字节。这就像为了开一盏灯,把整个楼的电闸都合上。
2. Bank化怎么破局
把缓存切成多个独立的Bank,每个Bank有自己的端口和控制逻辑。只要访问的是不同Bank,就能并行处理。
2.1 地址映射策略
最简单的映射是取模:
假设64字节块、4个Bank:
- 地址0x0000 → Bank 0
- 地址0x0040 → Bank 1
- 地址0x0080 → Bank 2
- 地址0x00C0 → Bank 3
- 地址0x0100 → Bank 0(循环)
这种交错(Interleaving)让连续地址分散到不同Bank,最大化并行性。
2.2 实际芯片的Bank配置
| 处理器 | 缓存层级 | Bank数 | 设计特点 |
|---|---|---|---|
| ARM Cortex-A8[1] | L2 | 1-4可配置 | 根据负载动态启用,平衡带宽与功耗 |
| Intel Core i7[2] | L1D | 4 | 支持每周期2次访问 |
| Intel Core i7[2] | L2 | 8 | 多核共享,更高并行度 |
| AMD Opteron[3] | L1D | 8 | 每Bank 64位宽,总线512位 |
| NVIDIA GPU | L2 | 16+ | 高并行,支持warp级访问 |
ARM Cortex-A8的L2缓存特别有意思——它支持1到4个Bank的软件可配置[1]。低负载时只开1个Bank省电,高负载时全开4个Bank提速。
3. Bank冲突:并行化的天敌
Bank化的前提是访问落在不同Bank。如果两个请求命中同一个Bank,就会冲突,必须串行处理。
3.1 冲突的性能代价
ISCA 2015的一项研究[4][5]测量了Bank冲突的影响:
- 测试配置:4周期发射到执行延迟的乱序处理器,双发射load能力
- Bank配置:4-Bank L1D缓存(类似Intel Core i7)
- 结果:Bank冲突导致平均4.7%性能损失(相比理想双端口缓存)
- 微指令重放:因冲突重放的微指令占总发射的5.1%
4.7%听起来不多,但在高性能计算中,每1%都很珍贵。
3.2 什么代码容易冲突
Stride = Block_Size × N:
// 假设64字节块,4个Bank
// Stride = 256字节(0x100)
for (int i = 0; i < N; i += 64) {
sum += array[i]; // 每次访问Bank 0
}
这种代码所有访问都落在Bank 0,其他Bank闲置,性能退化为单Bank。
矩阵转置:
// 行优先存储,按列访问
for (int j = 0; j < cols; j++) {
for (int i = 0; i < rows; i++) {
dst[j][i] = src[i][j]; // Stride = 行大小
}
}
如果行大小恰好是Bank数的倍数,就会产生严重冲突。
3.3 缓解冲突的方法
增加Bank数:
从4 Bank增加到8 Bank,冲突概率理论上减半。但面积和功耗也增加。
非线性映射:
用哈希函数替代简单取模:
这种XOR哈希能打乱规律性访问模式,减少冲突。
软件层面的Loop Interchange:
编译器优化可以把:
// 原始代码 - 可能冲突
for (j) for (i) dst[j][i] = src[i][j];
变成:
// 优化后 - 连续访问
for (i) for (j) dst[j][i] = src[i][j];
4. 硬件实现细节
4.1 Bank选择逻辑
地址的低位直接选择Bank,高位用于Tag比较和行内偏移。
以Intel Core i7的L1D为例(4 Bank,64字节行):
| 地址位 | 用途 |
|---|---|
| [5:0] | 块内偏移(64字节) |
| [7:6] | Bank选择(4 Bank) |
| [31:8] | Tag比较 |
这样Bank选择可以在1个周期内完成,不增加关键路径延迟。
4.2 多端口的假象
多Bank不是真正的多端口。每个Bank仍然是单端口,只是多个Bank可以并行。
如果两个请求命中同一Bank,必须仲裁。常用策略:
- 轮询(Round-Robin):公平但可能延迟关键路径
- 优先级:老请求优先,或load优先于store
- 年龄:最老的请求优先,保证前进
4.3 面积与功耗权衡
| Bank数 | 控制逻辑面积 | 动态功耗 | 峰值带宽 |
|---|---|---|---|
| 1 | 1x | 1x | 1x |
| 4 | ~1.3x | 0.6x(相同访问模式) | 4x |
| 8 | ~1.6x | 0.4x | 8x |
注意动态功耗列。多Bank能降低功耗,因为每次只激活一个Bank的小阵列,而不是整个大阵列。
ARM Cortex-A8的实测数据[1]:4 Bank配置比1 Bank节省约35%动态功耗(相同负载下)。
5. 与乱序执行的协同
多Bank缓存和乱序执行处理器是绝配。
5.1 动态调度避免冲突
乱序执行核可以重排指令,把访问不同Bank的load提前,冲突的推迟。
Intel Core i7的Memory Disambiguator会预测load-store别名,同时考虑Bank冲突,选择最优发射顺序。
5.2 Schedule Shifting优化
ISCA 2015提出的Schedule Shifting技术[5]:
当双发射两个load时,让第二个load的依赖指令晚一个周期发射。这样即使第二个load遇到Bank冲突,额外的周期可以被掩盖。
效果:
- 恢复2.8%的性能损失(从4.7%降到1.9%)
- 减少74.8%因Bank冲突导致的微指令重放
实现成本极低——只需要在调度器中加一个周期的偏移。
6. GPU中的极致Bank化
GPU对Bank化的需求比CPU更强烈。
6.1 Warp级并行访问
一个Warp(32线程)同时执行,每个线程可能访问不同地址。如果这32个地址分散在32个Bank,可以一次完成;如果都挤在一个Bank,需要32个周期。
6.2 Shared Memory的Bank冲突
NVIDIA GPU的Shared Memory有32个Bank(计算能力3.0+)。
无冲突访问:
// 每个线程访问不同Bank
int val = shared[threadIdx.x]; // Bank = threadIdx.x % 32
冲突访问:
// 所有线程访问Bank 0
int val = shared[0]; // 32周期串行
部分冲突:
// Stride=2,线程0,2,4...访问Bank 0,2,4...(偶数Bank)
// 线程1,3,5...访问Bank 1,3,5...(奇数Bank)
// 需要2周期完成
int val = shared[threadIdx.x * 2];
6.3 广播机制
如果32个线程都读同一个地址,GPU可以广播,只读一次分给所有线程。这不算是Bank冲突。
7. 实际调试:如何检测Bank冲突
Intel PMU(Performance Monitoring Unit)可以统计:
-
L1D_PEND_MISS.PENDING:L1D pending miss周期 -
L1D_PEND_MISS.FB_FULL:Fill Buffer满(可能由Bank冲突引起)
ARM Cortex-A8有L2CC寄存器可以监控L2 Bank使用情况[1]。
8. 总结
| 特性 | 单Bank | 多Bank |
|---|---|---|
| 峰值带宽 | 低 | 高(随Bank数线性增长) |
| 访问延迟 | 固定 | 可能因冲突增加 |
| 功耗效率 | 低(每次激活整个阵列) | 高(只激活一个Bank) |
| 面积开销 | 低 | 高(控制逻辑复制) |
| 实现复杂度 | 简单 | 需要仲裁和冲突处理 |
多Bank缓存是带宽和功耗的权衡艺术:
- Bank数太少:并行度不够,容易冲突
- Bank数太多:面积功耗爆炸,收益递减
实际设计的甜点区:
- L1缓存:4-8 Bank
- L2缓存:8-16 Bank
- GPU Shared Memory:32 Bank
理解Bank化,写代码时就能避免Stride陷阱,做体系结构设计时也能做出合理的权衡。
参考
================================================================================
-
ARM Cortex-A8 Technical Reference Manual. L2 Cache Bank Structure. ↩ ↩ ↩ ↩
-
Computer Systems Engineering, SJTU. Multibanked Caches Lecture Notes. ↩ ↩
-
AMD Opteron Processor Data Sheet. Cache banking implementation with 8 banks. ↩
-
ISCA 2015. Cost-Effective Speculative Scheduling in High Performance Processors. ↩
-
Hal-01193233. Cost-Effective Speculative Scheduling in High Performance Processors (Extended Version). ↩ ↩