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

顺序访问double数组时LLC缓存负载计数异常及高缺失率疑问

顺序数组访问的LLC缓存异常问题分析

一、基础疑问

为什么顺序数组访问会存在较高的缓存缺失率?

二、测试场景与问题详情

我编写了一段C代码用于理解perf工具与缓存机制,对double类型数组进行顺序访问,代码如下:

// test.c
#include <stdio.h>
#include <stdlib.h>
#include <emmintrin.h>
int main(int argc, char **argv) {
    size_t n = 10000000;
    double* arr;
    if (posix_memalign((void**)&arr, 64, n * sizeof(double)) != 0) {
        fprintf(stderr, "posix_memalign failed\n");
        exit(1);
    }
    for (size_t i = 0; i < n; i += 64) {
        _mm_clflush(&arr[i]);
    }
    for (size_t i = 0; i < n; i++)
        arr[i] = (double)i;
    double sum = 0;
    for (size_t i = 0; i < n; i++)
        sum += arr[i];
    printf("Sum: %f\n", sum);
    free(arr);
    return 0;
}

我采用无优化方式编译代码:

$ gcc -o test test.c

随后使用perf工具统计LLC相关性能计数器:

$ perf stat -e LLC-loads,LLC-loads-misses -- ./test

得到如下结果:

Sum: 49999995000000.000000
Performance counter stats for './test':
94,765 LLC-loads:u
91,979 LLC-loads-misses:u # 97.06% of all LL-cache accesses

0.082974254 seconds time elapsed
0.047802000 seconds user
0.034893000 seconds sys

现有两个疑问待解答:

  1. LLC-loads计数远低于预期,理论上该值应接近10000000/8(缓存行大小为64字节),是否存在perf未对程序整个运行时进行采样的可能?
  2. 缓存缺失率过高:预期每次读取缺失会加载包含8个double元素的缓存行,整体缺失率应接近1/8,且缓存预取机制应进一步降低缺失率,但实际缺失率高达97.06%,这是为什么?

三、环境信息

  • GCC版本:gcc (GCC) 15.2.0
  • Perf版本:perf version 4.18.0-553.84.1.el8_10.x86_64
  • CPU型号:Intel(R) Xeon(R) Gold 6430
  • L3缓存大小:61440K

问题解答

针对疑问1:LLC-loads计数远低于预期

首先得明确LLC-loads事件的定义:它统计的是需要从LLC(最后一级缓存)加载数据的请求,而非所有缓存行的访问请求。你的代码里有个关键逻辑:调用_mm_clflush刷掉数组缓存后,紧接着执行了写入操作arr[i] = (double)i——这个写入过程会自动把对应缓存行重新加载到L1/L2缓存中。

等到后续求和阶段读取数组时,数据大多还停留在L1/L2缓存,根本不需要去LLC取数据,所以LLC-loads的计数自然远低于你计算的10000000/8(约125万)。另外,你用的LLC-loads:u只统计用户态的LLC请求,内核态访问不会被计入,但这不是主要原因,核心还是低层缓存命中占了绝大多数。

针对疑问2:97%的LLC缺失率

这个高缺失率其实是个“假象”,核心原因是LLC的访问基数太小:

  1. 你的数组总大小是80MB(1000万×8字节),而L3缓存只有60MB,整个数组无法完全放入L3。写入阶段结束后,部分缓存行已经被挤出L3,等到求和阶段遍历到这些位置时,就需要从LLC甚至内存重新加载——这些请求才会被统计到LLC-loads里,而这类请求本身就是因为低层缓存没命中才触发的,所以缺失率自然极高。
  2. 缓存预取的作用被削弱:写入阶段的预取已经把数据加载到低层缓存,但求和阶段遍历到后半段时,前面的缓存行已经因为容量不足被驱逐,预取的缓存行也很快被替换,导致后续读取不得不重新请求LLC/内存。
  3. 无优化编译的影响:你没加任何优化编译代码,编译器生成的指令没有充分利用CPU的预取机制,预取效率低下,进一步拉高了缺失率。如果加上-O2优化,编译器会生成更高效的遍历代码,预取机制能更好地发挥作用,缺失率会降到接近预期的水平。

另外,你可以尝试修改代码:把_mm_clflush循环移到写入之后、求和之前——这样能确保求和阶段的读取从LLC/内存开始,此时LLC-loads会更接近预期的125万,缺失率也会降到正常的1/8左右(甚至更低,因为预取会生效)。


内容的提问来源于stack exchange,提问作者user180574

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 12:37:38