Appearance
编译优化:-O 级别 / 内联 / LTO / PGO / march / fast-math
HPC 面试围绕“怎么让编译帮你更快”。答好 = 说清各级别做什么、为什么、什么时候不该激进。
一、常见优化等级
| 开关 | 含义 |
|---|---|
-O0 | 不优化:忠实代码、方便调试、全符号可看(默认 debug) |
-O1 | 基本优化:消除死代码、常量折叠、大部分通用 |
-O2(release 默认) | 更多:内联、自动向量化(dep)、循环变换的一大部分;一般代码的最优 |
-O3 | O2 之上再激进:更积极内联、更多向量化、函数内分配提升、可能展开循环。稳定但有时二进制更大、Cache 反而变差 |
-Os | 为减小代码尺寸(偏嵌入式/内存紧张) |
-Ofast | -O3 + 允许违标准假设(等同 -ffast-math 等):宁可用 -O3 + 明确 fast-math 场景 |
经验法则:
- 一般 server 用
-O2稳;数值内核可试-O3 -march=native,如性能不回退就用之。 -O3不是必然更快:更大的内联/展开可能爆 icache/增加寄存器压力 → 要以基准为准。-Og:优化但仍可调试,开发用。
二、内联 inline(重要细节)
inline(关键字)只是提示;编译器自行决定(-O0 下基本都不内联;-O2+ 自动内联启发式)。真正的内联好处:免调用开销 + 让常量传播/消除越深(跨函数优化机会)。
跨翻译单元限制:
- 每个 TU 单独优化,别的 .cpp 的函数体看不+到 → 无法内联跨 .cpp 的函数。
- 解决:
- 头文件 inline(成员函数/模板天然跨 TU);
- LTO(链接期统一 IR 后内联跨 TU)。
- attribute((always_inline))/
[[gnu::always_inline]]可强制;不常用且小心膨胀。
三、LTO(link-time optimization)
原理:各 .o 先存中间表示(IR/GIMPLE)而不立即降成最终机器码,链接期把全部 IR 汇集一次统一再优化(跨函数/跨 TU 内联、常量传播、死代码消除)。
- 收益:跨模块内联、常量折叠穿透边界、整程序见树、去除未用符号。
- 代价:链接明显变慢、内存高、与某些编码不符(函数指针被优化掉需注意)、需要库都用 LTO。
- 用法:
g++ -flto(链接与编译都加);还有-fno-fat-lto-objects等。
四、PGO(profile-guided optimization)
流程:
1) -fprofile-generate 编译 → 跑代表性负载(收集分支/执行频)
2) -fprofile-use 重新编译 → 按 hot/cold 布局、分支预测、决定内联/展开、局部性摆放- 收益:按真实流量而非静态猜测优化分支/热块/内联。
- HPC/服务端(负载可代表性)收益明显;注意 profile 与真实负载偏差会反向伤害,需跑代表性 dataset + 回归。
五、-march=native 与 -mtune
-march=native:启用本机 CPU 全套指令(如 AVX2/AVX-512、FMA、BMI)——向量化关键,能显著提速数值 kernel。- 代价:产物不再能跑到旧 CPU(非法指令崩溃)→ 发布到异构集群需用通用
-march=x86-64-v3或运行时 dispatch。 -mtune:不启用新指令,只按指定 uarch 选调度。
六、-ffast-math 危险(重点讲清)
-ffast-math 集合:忽略 NaN/Inf 假设、允许重结合违反 IEEE(如 a+(b+c) 改成 (a+b)+c)、不保证舍入一致、-0.0、假设无 signed zero 等 → 数值结果可能悄悄错误。
- HPC:若你不做精确 IEEE 且确保算法条件良好可开;分布式/加密/需要 bit 级确定结果 → 不要开。
- 对比:
-fno-math-errno/-freciprocal-math单独开小优化更稳。
七、向量化提示(编译器角度)
- 要自动向量化需:循环可数、无别名歧义(
__restrict__)、无控制流(或可 if-conv)、数据对齐(alignas(64)让 compiler 用对齐 load)。 #pragma omp simd/#pragma GCC ivdep提示可向量化意图。- 想确认生成 SIMD:反汇编看
movaps/vaddps或-fopt-info-vec -fopt-info-vec-missed。
八、给面试官一套“把 kernel 编译调快”的口径
- 先
-O2基准; - 数值热内核:
-O3 -march=native -flto,必要时-ffast-math(项目验证数值稳定); - 用 intrinsic 显式 SIMD 或让编译器 auto-vector,检查是否真的向量化;
- LTO 打开跨 TU 内联,profile 指导再上 PGO——每步都 benchmark,别依赖直觉;
- 若开 O3 变慢(icache/内存),回退 O2/Og。
九、快速自测
- inline 关键字保证内联吗?(不,编译器决定)
- 为什么 .cpp 里的函数在别的 TU 调不到 = 无法内联?跨 TU 用 LTO 才能拿函数体。
- -march=native 在异构机台的坑?—— 非法指令;用 runtime dispatch。
- 什么时候谨慎 fast-math/为什么?