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

寄存器到高频访问变量的意外慢性能问题求助

缓存性能分析疑问:为何mov指令占比最高?

背景与测试代码

我正在通过以下示例学习缓存工作机制:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

typedef uint32_t data_t;
const int U = 10000000;   // size of the array. 10 million vals ~= 40MB
const int N = 100000000;  // number of searches to perform

int main() {
  data_t* data = (data_t*) malloc(U * sizeof(data_t));
  if (data == NULL) {
    free(data);
    printf("Error: not enough memory\n");
    exit(-1);
  }

  // fill up the array with sequential (sorted) values.
  int i;
  for (i = 0; i < U; i++) {
    data[i] = i;
  }

  printf("Allocated array of size %d\n", U);
  printf("Summing %d random values...\n", N);

  data_t val = 0;
  data_t seed = 42;
  for (i = 0; i < N; i++) {
    int l = rand_r(&seed) % U;
    val = (val + data[l]);
  }

  free(data);
  printf("Done. Value = %d\n", val);
  return 0;
}

perf分析结果

使用perf record ./sum和perf report分析慢随机访问循环,得到相关注解:

0.05 │       mov    -0x18(%rbp),%eax                                                                 ▒
  0.07 │       mov    -0x10(%rbp),%rcx                                                                 ▒
       │       movslq -0x20(%rbp),%rdx                                                                 ▒
  0.03 │       add    (%rcx,%rdx,4),%eax                                                               ▒
 95.39 │       mov    %eax,-0x18(%rbp)                                                                 ▒
  1.34 │       mov    -0x14(%rbp),%eax                                                                 ▒
       │       add    $0x1,%eax                                                                        ◆
       │       mov    %eax,-0x14(%rbp)

各内存位置对应的变量:

  • -0x18 存储 val
  • -0x10 存储 data
  • -0x14 存储 i
  • -0x20 存储 l

左侧数字为指令耗时占比。

疑问

我原本认为add (%rcx,%rdx,4),%eax指令耗时最多,因为它需要对data[l]执行随机访问加载——我的L1缓存大小为64KB(即16K个整数),该操作命中L1缓存的概率仅约0.16%,理应较慢。但实际耗时最多的是将寄存器%eax的值存入val的mov %eax,-0x18(%rbp)指令,而val是高频访问变量,肯定在缓存中。请问这是为什么?


原因分析

这是CPU乱序执行与perf采样机制共同作用的结果:

  1. 乱序执行的延迟隐藏
    CPU会通过乱序执行来掩盖内存访问的延迟。当add (%rcx,%rdx,4),%eax发起慢内存加载时,CPU不会原地等待,而是先调度执行后续的mov %eax,-0x18(%rbp)指令。但这个mov指令实际无法完成,它必须等前面的加载操作完成、add指令执行结束后,才能将正确的值写回内存。

  2. perf采样的计数逻辑
    perf基于时钟周期采样统计指令耗时占比:当CPU因等待内存加载进入空闲周期时,采样计数器会将这些周期归属给当前处于“等待提交结果”状态的指令。在这里,mov %eax,-0x18(%rbp)就是那个被“背锅”的指令——它一直在等待前面的加载完成,所以大部分内存等待周期都被算到了这个指令头上。

  3. 缓存未命中才是真正瓶颈
    你的判断没错:随机访问data[l]确实是性能瓶颈,它产生的数百个时钟周期延迟,被CPU乱序执行引擎转移到了后续的mov指令上。perf的采样结果只是把延迟的统计“归属”到了后续指令,但程序变慢的核心原因依然是缓存未命中导致的内存加载慢。

可以通过perf stat -e cache-misses ./sum验证:你会看到极高的缓存未命中次数,这直接证明了内存加载延迟是性能问题的根源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:38:29