You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何测试代码无法观测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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 01:31:22