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

为何GCC在-O3优化(x86)下每次循环都mov数组首地址访问元素?

为何GCC -O3优化后循环仍重复加载全局数组首地址?

问题背景

我为研究TLB访问/缺失统计设计了测试样本:向数组的每第4096个元素写入1,数组大小为10000*4096字节。启用-O3优化后,我预期只会产生10000次TLB存储操作,但实际生成的汇编代码中,每次循环都会加载数组首地址,额外带来10000次TLB加载。

查看汇编发现循环流程为:

  1. 将数组首地址移入寄存器
  2. 向偏移后的数组地址写入1
  3. 增加索引
  4. 跳回步骤1

数组首地址并未改变,为何步骤1要在每次循环都执行?我期望首地址仅加载一次,循环跳回写入步骤即可。

测试代码

#include <stdlib.h>

#define REPEAT 10000
#define PAGESIZE 4096
#define PAGES 10000

char *data = NULL;

inline void test_function()
{
    for (int i = 0; (i < PAGES * PAGESIZE); i += (PAGESIZE)) {
        data[i] = 1;
    }
}


int main()
{
    data = (char *) malloc(PAGES * PAGESIZE);
    for (int idx = 0; idx < REPEAT; idx++)
        test_function();
    free(data);
    return 0;
}

GCC生成的汇编

1090:       mov    rdx,QWORD PTR [rip+0x2f81]        # 4018 <data>
    1097:       mov    BYTE PTR [rdx+rax*1],0x1
    109b:       add    rax,0x1000
    10a1:       cmp    rax,0x2710000
    10a7:       jne    1090 <main+0x30>

perf统计结果

当重复执行100000次后,每次函数调用会产生:

  • 10K次L1缓存加载、10K次L1缓存存储
  • 10K次TLB加载、10K次TLB存储
  • TLB加载缺失几乎为0,TLB存储缺失10K次

数组首地址的加载始终命中TLB缓存,但仍会被重复访问。

平台信息

  • CPU:Intel(R) Core(TM) i7-10610U
  • 系统:Ubuntu 22.04.3 LTS
  • 编译器:g++ 11.4.0
  • 编译命令:g++ ./tlb.cpp -O3 -g -o gcc.out

原因分析

  1. 全局变量的内存可见性约束
    data是全局变量,C++内存模型要求编译器必须保证每次访问全局变量时都读取其最新值——即使你的代码里没有显式修改它,编译器也无法排除其他线程、信号处理函数等外部因素修改它的可能。因此无法将data的值持久保存在寄存器中跨循环复用。

  2. 寄存器分配与优化限制
    虽然理论上寄存器可以缓存data的地址,但受限于全局变量的可见性规则,编译器不敢做这种优化。即使寄存器资源充足,内存可见性的优先级也高于寄存器复用优化。


解决方法

方法1:将全局变量改为局部参数传递

把data作为参数传入test_function,编译器能确定它在函数执行期间不会被外部修改,从而优化掉重复读取:

#include <stdlib.h>

#define REPEAT 10000
#define PAGESIZE 4096
#define PAGES 10000

inline void test_function(char *data)
{
    for (int i = 0; i < PAGES * PAGESIZE; i += PAGESIZE) {
        data[i] = 1;
    }
}

int main()
{
    char *data = (char *) malloc(PAGES * PAGESIZE);
    for (int idx = 0; idx < REPEAT; idx++)
        test_function(data);
    free(data);
    return 0;
}

方法2:使用__restrict__关键字

如果必须保留全局变量,用__restrict__告诉编译器该指针不会被其他指针别名引用,且不会被外部修改:

char *__restrict__ data = NULL;

这会让编译器认为data的值在循环期间不会改变,从而将其缓存到寄存器中。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 22:50:03