必须手动设置编译选项为-O2或-O3(推荐-O2 -march=native),启用Show compilation output并禁用Demangle symbols,才能通过汇编密度与模式识别内联、循环展开和向量化效果。

直接粘贴代码就能看汇编,但默认不显示关键优化信息
Compiler Explorer(Godbolt)开箱即用,但新手常误以为“点了编译就完事”——g++ 或 clang++ 默认启用 -O0,生成的汇编充斥着栈帧操作、冗余寄存器搬运,完全看不出性能瓶颈在哪。真正用于性能分析,必须手动改编译选项。
- 右上角
Compilation options输入框里,把-O0换成-O2或-O3(推荐-O2 -march=native模拟本地环境) - 勾选
Show compilation output,确认没有报错或警告(比如未定义行为会触发UBSan干预,导致汇编失真) - 禁用
Filters → Demangle symbols:开启后函数名被还原成 C++ 符号(如_Z3fooi→foo(int)),反而干扰你观察内联和模板实例化痕迹
识别内联、循环展开、向量化是否生效
性能优化的核心判断依据,不在代码行数,而在汇编指令的“密度”与“模式”。Godbolt 的左侧源码和右侧汇编是逐行映射的(鼠标悬停某行 C++,右侧高亮对应指令),但需主动找信号:
- 函数调用消失 → 很可能被内联:源码中
bar(x)没有对应call bar,而是直接看到add eax, 1等原函数体指令 - 循环体重复出现多次 → 编译器做了展开:源码一个
for (int i=0; i,汇编里连续四组相同计算指令,无jmp回跳 - 出现
vpaddd、vmovdqu等以v开头的指令 → AVX/SSE 向量化成功(前提是数据对齐且无依赖) - 若期望向量化却看到
mov eax, DWORD PTR [rdi]单字节加载 → 检查数组是否指针别名(加restrict)、或循环是否有条件分支打断向量友好性
对比不同编译器/版本的汇编差异
Clang 和 GCC 对同一段代码的优化策略常不同,尤其在模板推导、constexpr 展开、内存模型处理上。Godbolt 的多编译器并排视图是性能调优的关键工具:
- 点击左上角
Add new…,添加clang 17.0.1和gcc 13.2,保持编译选项一致(如都用-O2 -std=c++20) - 重点对比:Clang 常更激进地做尾递归优化,GCC 在循环不变量提取上有时更稳;C++20
std::span的边界检查,Clang 可能完全删掉,GCC 可能保留cmp+jae - 不要只看“谁生成指令少”,而要看关键路径:比如热点循环中,Clang 用
lea rax, [rdi + rsi*4]一条指令完成地址计算,GCC 拆成shl rsi, 2+add rax, rdi—— 前者更优
避免被调试符号和运行时开销干扰真实汇编
Godbolt 默认链接标准库,std::vector::push_back 这类函数会展开成几十行汇编,掩盖你关心的核心逻辑。要聚焦纯算法部分:
立即学习“C++免费学习笔记(深入)”;
- 用
static或[[gnu::noinline]]标记待分析函数,防止被外联到其他上下文中 - 避免
#include或std::cout:它们引入大量初始化代码和锁操作,污染汇编主体 - 慎用
std::string:其 small-string optimization 实现因编译器而异,汇编差异大且与性能无关;改用const char*或std::array - 若需模拟真实调用场景,用
__attribute__((used))强制保留函数符号,再在右侧汇编中搜索该函数名(如my_hot_loop),跳过所有启动代码
int my_hot_loop(const int* a, const int* b, int n) {
int sum = 0;
for (int i = 0; i < n; ++i) {
sum += a[i] * b[i];
}
return sum;
}这段代码在 clang++ -O3 -mavx2 下大概率生成带 vpmulld 和 vpaddd 的向量化循环,在 gcc -O2 下可能仍是标量循环——差异不是 bug,而是优化器对数据依赖的不同建模。盯住那几行核心计算指令,比纠结“为什么没向量化”更有价值。











