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

为何缓存命中率理论更高的第二个C程序在M1芯片上运行性能更差?

问题核心错误点分析

问题背景

给出的两个C语言程序如下:

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

typedef unsigned long long u64;

int program_1(u64* a, u64* b)
{
  const u64 lim = 50l * 1000l * 1000l;
  // Reads arrays
  u64 sum = 0;
  for (u64 i = 0; i < lim * 100; ++i) {
    sum += a[i % lim];
    sum += b[i % lim];
  }

  printf("%llu\n", sum);
  return 0;
}


int program_2(u64* a, u64* b)
{
  const u64 lim = 50l * 1000l * 1000l;
  // Reads arrays
  u64 sum = 0;
  for (u64 i = 0; i < lim * 100; ++i) {
    sum += a[i % lim];
  }
  for (u64 i = 0; i < lim * 100; ++i) {
    sum += b[i % lim];
  }

  printf("%llu\n", sum);
  return 0;
}

原有预期认为程序1交替访问数组会导致缓存频繁失效性能更差,但实际测试程序1速度更快,核心是原有缓存认知存在三个关键错误:

  • 错误判断了L1缓存的容量对双数组访问的容纳能力
    原预期错误认为交替访问a、b数组时,后加载的缓存行会直接挤出先加载的另一数组的缓存行。实际上M1的L1数据缓存为64KB,以64字节缓存行计算可容纳1024个缓存行,同时存放a、b两个数组的数十个缓存行完全没有压力,根本不会出现交替访问就互相淘汰的情况。
    实际程序1的缓存命中逻辑和程序2几乎一致:访问a[0]触发miss后会加载连续8个u64(a[0]~a[7])进入缓存,随后访问b[0]触发miss加载b[0]~b[7]进入缓存,接下来7次对a[1]~a[7]、b[1]~b[7]的访问全部命中,和程序2的缓存命中率没有本质差异,不会出现原预期的次次miss的情况。
  • 完全忽略了循环本身的执行开销
    程序1仅需要执行1次5*10^9次的循环迭代,程序2需要执行2次总计1*10^10次的循环迭代。每次循环都包含i自增、边界判断、跳转这几个固定操作,程序2的循环控制开销直接是程序1的两倍,这部分额外开销远大于两者几乎可以忽略的缓存命中率差异。
  • 未考虑M1芯片的专用硬件优化
    M1的L1缓存本身带宽极高,同时内置的硬件预取器可以同时识别多个连续访存流,程序1交替访问a、b两个连续数组的模式完全可以被预取器识别,同步预取两个数组的后续缓存行,进一步抹平了两者的缓存性能差异。

此外程序1的单循环结构更利于CPU做指令级并行优化,同一个循环内的两次访存、两次加法可以被CPU乱序调度并行执行,进一步提升了执行效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 10:57:02