为何测试代码无法观测4/8路组相联缓存的性能影响?
你尝试复现组相联缓存冲突实验但未观测到预期效果,核心是代码的访问模式、编译优化、缓存残留等问题抵消了缓存冲突的性能差异,具体问题及修正方案如下:
1. 访问模式未命中缓存冲突核心逻辑
组相联缓存性能下降的本质是多个内存地址映射到同一个缓存组,且数量超过缓存组的路数,导致缓存行被频繁驱逐、重新加载。你的遍历方式idx = (idx + step) % arr_len存在两个关键问题:
- 当
step与arr_len(2^28)不互质时,只会遍历数组的子集(比如step=2时仅访问偶数索引),且重复访问这些子集元素,缓存行持续驻留,无法触发冲突驱逐。 - 未针对性构造「不同地址映射到同一缓存组」的场景:需要让访问的地址在缓存组计算上完全重叠,而非单纯跨步遍历。比如对于8路组相联缓存,当同时访问9个映射到同一组的缓存行时,才会出现明显的缓存缺失。
2. 编译器优化可能抵消内存访问开销
代码中sum是uint8_t类型,每次累加arr[idx]会被截断为8位。编译器可能识别到sum的最终值对输出无实质影响(仅作为返回值),从而优化掉部分内存访问操作,导致测试结果失真。建议给arr加上volatile修饰,强制编译器生成真实的内存访问指令。
3. 缓存预热残留缩小性能差异
你按step从小到大依次测试,前序测试(如step=1)会把整个数组加载到CPU的L3缓存中。后续测试即使触发L1/L2缓存冲突,也会从L3而非内存加载数据,延迟差异被大幅缩小,无法观测到明显的性能下降。需在每个step测试前用足够大的临时数组冲刷缓存。
4. 测试循环逻辑冗余
内层循环执行arr_len次,对于大step会重复访问同一子集元素,反而让缓存行保持命中。正确做法是固定访问次数,确保每次访问不同的缓存行,而非重复访问同一元素。
#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #define CACHE_FLUSH_SIZE (12 * 1024 * 1024) // 大于E5-1620 v4的L3缓存(10MB) int main() { srand(time(NULL)); const size_t arr_len = 256 * 1024 * 1024; volatile uint32_t* arr = malloc(sizeof(uint32_t) * arr_len); uint32_t* flush_arr = malloc(sizeof(uint32_t) * CACHE_FLUSH_SIZE); // 初始化测试数组和缓存冲刷数组 for (size_t i = 0; i < arr_len; ++i) { arr[i] = rand(); } for (size_t i = 0; i < CACHE_FLUSH_SIZE; ++i) { flush_arr[i] = 0; } struct timespec ts; uint8_t sum = 0; double delta, t0; size_t idx; const size_t access_count = 100 * 1024 * 1024; // 固定访问次数,避免子集重复 for (size_t step = 1; step < 2049; ++step) { // 冲刷缓存,消除前序测试的缓存残留 for (size_t i = 0; i < CACHE_FLUSH_SIZE; ++i) { sum += flush_arr[i]; } // 计时开始 timespec_get(&ts, TIME_UTC); t0 = ts.tv_sec + ts.tv_nsec / 1e9; idx = 0; for (size_t i = 0; i < access_count; ++i) { sum += arr[idx]; idx = (idx + step) % arr_len; } // 计时结束并输出 timespec_get(&ts, TIME_UTC); delta = ts.tv_sec + ts.tv_nsec / 1e9 - t0; printf("%lu, %lf\n", step, delta); } free((void*)arr); free(flush_arr); return sum; }
针对你的Intel Xeon E5-1620 v4,其缓存参数为:
- L1数据缓存:32KB,8路组相联,64字节缓存行(共64组)
- L2缓存:256KB,8路组相联,64字节缓存行(共512组)
- L3缓存:10MB,20路组相联,64字节缓存行(共2048组)
当step满足(step * 4) / 64 % 64 == 0时(即step为1024的倍数),所有访问的地址都会映射到L1缓存的同一组,此时访问次数超过8次就会触发冲突,性能会明显下降,可重点观察step=1024、2048附近的测试结果。
内容的提问来源于stack exchange,提问作者D.J. Elkind

