利用编译器等优化程序¶
gcc文档:https://gcc.gnu.org/onlinedocs/
不同C++编译器的优化能力比较
通用优化
| 优化方法 | Gnu | Clang | Microsoft | Intel Classic | Intel LLVM based |
|---|---|---|---|---|---|
| 函数内联 | x | x | x | x | x |
| 常量折叠 | x | x | x | x | x |
| 常量传播 | x | x | x | x | x |
| 循环中的常量传播 | x | x | x | x | x |
| 指针消除 | x | x | x | x | x |
| 公共子表达式消除 | x | x | x | x | x |
| 寄存器变量 | x | x | x | x | x |
| 活跃范围分析 | x | x | x | x | x |
| 合并相同分支 | x | x | x | x | x |
| 消除跳转 | x | x | x | x | x |
| 尾调用优化 | x | x | x | x | x |
| 移除总是为假的分支 | x | x | x | x | x |
| 循环展开,数组循环 | x | excessive | x | excessive | x |
| 循环展开,结构体 | x | x | x | x | x |
| 循环不变代码外提 | x | x | x | x | x |
| 数组元素的归纳变量 | x | x | x | x | x |
| 其他整数表达式的归纳变量 | - | x | - | x | x |
| 浮点表达式的归纳变量 | - | - | - | - | - |
| 整数的多重累加器 | - | x | x | x | - |
| 浮点的多重累加器 | - | x | x | x | - |
| 虚函数调用优化(Devirtualization) | x | x | - | - | x |
| 基于性能分析的优化 | x | x | x | x | x |
| 全程序优化 | x | x | x | x | x |
整数代数简化
| 优化方法 | Gnu | Clang | Microsoft | Intel Classic | Intel LLVM based |
|---|---|---|---|---|---|
| 交换律(a+b=b+a, a_b=b_a) | x | x | x | x | x |
| 结合律((a+b)+c=a+(b+c)) | - | x | x (仅乘法) | x | x |
| 分配律(a_b+a_c=a*(b+c)) | x | x | x | x | x |
| a+b+c+d=(a+b)+(c+d)(改进并行性) | - | x | - | x | - |
| a_b_c_d=(a_b)_(c_d)(改进并行性) | - | x | - | x | x |
| x^8=((x²)²)² | x | x | - | x | x |
| a+a+a+a=a*4 | x | x | x | x | x |
| 霍纳法则(a_x³+b_x²+c*x+d) | x | x | x | x | x |
| -(-a)=a | x | x | x | x | x |
| a-(-b)=a+b | x | x | x | x | x |
| a-a=0 | x | x | x | x | x |
| a+0=a | x | x | x | x | x |
| a*0=0 | x | x | x | x | x |
| a*1=a | x | x | x | x | x |
| (-a)_(-b)=a_b | x | x | x | x | x |
| a/a=1 | x | x | x | - | x |
| a/1=a | x | x | x | x | x |
| 0/a=0 | x | x | x | - | x |
| 乘以常量=移位和加法 | x | x | x | x | x |
| 除以常量=乘法和移位 | x | x | x | x | x |
| 除以2的幂=移位 | x | x | x | x | x |
| (-a-b)=(ab) | x | x | x | - | x |
| (a+cb+c)=(ab) | x | x | x | x | x |
| !(a=b) | x | x | x | x | x |
| (a<b && b<c && a<c)=(a<b && b<c) | x | - | - | - | x |
浮点代数简化
| 优化方法 | Gnu | Clang | Microsoft | Intel Classic | Intel LLVM based |
|---|---|---|---|---|---|
| 交换律(a+b=b+a, a_b=b_a) | x | x | x | x | x |
| 结合律(a+b+c=a+(b+c)) | x | x | - | x | x |
| 结合律(a_b_c=a(bc)) | x | x | - | - | x |
| 分配律(a_b+a_c=a*(b+c)) | x | x | x | x | x |
| a+b+c+d=(a+b)+(c+d) | x | x | x | - | x |
| 霍纳法则(a_x³+b_x²+c*x+d) | x | x | x | x | x |
| x^8=((x²)²)² | x | x | - | - | x |
| a+a+a+a=a*4 | x | x | x | - | x |
| -(-a)=a | x | x | x | x | x |
| a-(-b)=a+b | x | x | x | x | x |
| a-a=0 | x | x | x | x | x |
| a+0=a | x | x | x | x | x |
| a*0=0 | x | x | x | x | x |
| a*1=a | x | x | x | x | x |
| (-a)_(-b)=a_b | x | x | x | x | x |
| a/a=1 | x | x | - | - | x |
| a/1=a | x | x | x | x | x |
| 0/a=0 | x | x | x | - | x |
| (-a-b)=(ab) | x | x | x | - | x |
| (-a>-b)=(a<b) | x | x | x | x | x |
| 除以常量=乘以倒数 | x | x | x | x | x |
布尔代数简化
| 优化方法 | Gnu | Clang | Microsoft | Intel Classic | Intel LLVM based |
|---|---|---|---|---|---|
| 无分支的布尔运算 | x | x | - | rarely | x |
| 交换律(a&&b=b&&a) | x | x | - | x | x |
| 结合律(a&&b&&c=a&&(b&&c)) | - | - | - | x | - |
| 分配律((a&&b) | (a&&c)=a&&(b | c)) | x | x | - |
| 分配律((a | b)&&(a | c)=a | (b&&c)) | x | x |
| 德摩根定律(!a&&!b=!(a | b)) | x | x | - | - |
| !(!a)=a | x | x | x | x | x |
| a&&!a=false, a | !a=true | x | x | x | |
| a&&true=a, a | false=a | x | x | x | |
| a&&false=false, a | true=true | x | x | x | |
| a&&a=a | x | x | x | x | x |
| (a&&b) | (a&&!b)=a | - | x | x | x |
| (a&&b) | (!a&&c)=a?b:c | - | x | x | x |
| (a&&b) | (!a&&c) | (b&&c)=a?b:c | - | - | x |
| (a&&b) | (a&&b&&c)=a&&b | x | x | x | x |
| (a&&!b) | (!a&&b)=a XOR b | x | x | - | - |
位运算整数简化
| 优化方法 | Gnu | Clang | Microsoft | Intel Classic | Intel LLVM based |
|---|---|---|---|---|---|
| 交换律(a&b=b&a) | x | x | x | x | x |
| 结合律(a&b&c=a&(b&c)) | x | x | x | x | x |
| 分配律((a&b) | (a&c)=a&(b | c)) | x | x | x |
| 分配律((a | b)&(a | c)=a | (b&c)) | x | x |
| 德摩根律a&b=(a | b) | x | x | x | x |
| (a)=a | x | x | x | x | x |
| a&~a=0, a | ~a=-1 | x | x | x | x |
| a&-1=a, a | 0=a | x | x | x | x |
| a&0=0 | x | x | x | x | x |
| a | -1=-1 | x | x | x | x |
| a&a=a, a | a=a | x | x | x | x |
| (a&b) | (a&~b)=a | x | x | x | x |
| (a&b) | (~a&c) | (b&c) = (a&b) | (~a&c) = a ? b : c | - | - |
| (a&b) | (a&b&c)=a&b | x | x | x | x |
| (a&!b) | (~a&b)=a^b | x | x | x | x |
| ab=ab | x | x | x | x | x |
| a&b&c&d=(a&b)&(c&d) | - | x | - | x | x |
| a<<b<<c=a<<(b+c) | x | x | x | x | x |
向量优化
| 优化方法 | Gnu | Clang | Microsoft | Intel Classic | Intel LLVM based |
|---|---|---|---|---|---|
| 循环自动向量化 | x | x | x | x | x |
| 带分支的自动向量化 | x | x | x | x | x |
| 使用掩码的条件指令 | x | x | - | x | x |
| 合并条件零到掩码指令 | x | x | - | x | x |
| 合并广播到指令 | x | x | - | - | x |
| 合并混合到掩码指令 | x | x | - | x | x |
| 合并布尔AND到掩码比较 | x | x | - | x | x |
| 消除全为真的掩码 | x | x | x | x | x |
| 消除全为假的掩码 | x | x | - | x | x |
| 数学库函数向量化 | x | x | x | x | x |
| 跨向量排列优化 | - | x | - | - | x |
"x"表示支持该优化,"-"表示不支持该优化,"excessive"表示过度优化(如过度循环展开)。
调整编译器选项¶
-
最基本也是最重要的一步是启用编译器的优化标志。通常,以下几个是必备的:
-
-O3: 启用与机器无关的激进优化。这是大多数编译器提供的最高级别优化之一,会进行大量的代码转换,如循环展开、函数内联、死代码消除等。 -march=native或-march=<CPU_ARCH>: 启用针对特定 CPU 架构的优化。这会指示编译器生成针对当前机器或指定架构(如core-avx2、arm64)的指令集优化代码,从而利用 CPU 特定的高性能指令(如 AVX、AVX2、AVX-512、NEON)。-flto(Link Time Optimization)/-fipo(Inter-Procedural Optimization): 启用链接时优化(LTO),也称为过程间优化(IPO)。通常,编译器一次只能优化一个编译单元(源文件)。LTO 允许编译器在链接阶段对整个程序进行全局分析和优化,从而发现跨文件边界的优化机会,如更激进的函数内联、死代码消除和数据流优化。-fwhole-program选项启用 WPO 以启用过程间优化,将整个代码库视为一个整体程序,与 LTO 特性类似。-
-fprofile-generate: 在编译时插入性能探针,用于记录程序运行时的执行频率(如函数调用次数、分支跳转次数等)。-fprofile-use: 编译器利用带探针的程序执行收集到的热点数据,对频繁执行的代码路径进行更激进的优化,对冷路径则可能减少优化开销。 -
其他重要选项: 还有许多其他选项会影响性能,例如:
-
-ffast-math: 允许编译器重新关联浮点运算,可能改变运算顺序,从而启用更多优化(如浮点运算的向量化)。但需谨慎使用,因为它可能导致结果的微小不精确,并涉及 NaN、无穷大等特殊行为。在数据库中,这通常只在对数值精度要求不那么严格的分析型查询中使用。 -mprefer-vector-width=###: 如果不希望使用特定的重型 AVX 指令(如 AVX-512,在某些老旧 CPU 上可能导致降频),可以将其设置为 128 或 256,以固定最高向量宽度。
GCC最常用的编译选项
| 编译指令 | 说明 |
|---|---|
| -Wall -Wextra –Wpedantic | 加入警告信息 |
| -O3 | 最高级别常规优化,打开循环展开、向量化、函数内联等激进策略。 |
| -g | 保留调试信息 |
| -std=c++20 | 使用 C++20 标准 |
| -march=native | 让编译器按“当前编译机”的 CPU 特性(AVX2、SSE4.2、BMI…)生成指令 |
| -fopenmp | 启用 OpenMP 多线程并行 |
| -fno-exception | 禁用 C++ 异常机制 |
| -fno-rtti | 禁用运行时类型信息(typeid, dynamic_cast) |
| 链接器行为指示 | –l library 选项传递给链接器以指定要链接的可执行文件与哪个库。指定 –static 参数以防止与共享库链接并选择静态库。 |
GCC 优化级别简单对比
| 优化类别 / 典型开关 | -O0 | -O1 | -O2 | -O3 |
|---|---|---|---|---|
| - 禁用所有优化 | ✅ | ❌ | ❌ | ❌ |
| - 保留完整调试信息(-g 效果最佳) | ✅ | ✅ | ✅ | ✅ |
| 基本块重排 / 合并 (-freorder-blocks, -fmerge-constants) | ❌ | ✅ | ✅ | ✅ |
| 指令调度 (-fschedule-insns, -fschedule-insns2) | ❌ | ✅ | ✅ | ✅ |
| 强度削弱(乘→移位、除→乘倒数) (-fstrength-reduce) | ❌ | ✅ | ✅ | ✅ |
| 内联函数 (-finline-functions-called-once, -finline-small-functions) | ❌ | 部分 | ✅ | ✅+更激进 |
| 循环优化 | ||||
| - 循环展开 (-funroll-loops) | ❌ | ❌ | 部分 | ✅ |
| - 循环向量化 (-ftree-vectorize) | ❌ | ❌ | ✅ | ✅ |
| - 循环交换 / 分块 / 剥离 (-floop-interchange, -floop-block) | ❌ | ❌ | ❌ | ✅ |
GCC 优化级别详细表格
当启用 -O1 时,GCC 优化的标志
| 优化标志 | 作用说明 |
|---|---|
| -fdce 和 -fdse | 执行死代码消除(DCE)和死存储消除(DSE),移除永远不会执行的代码和无用的存储操作 |
| -fdelayed-branch | 重新排序指令,以尝试在延迟分支指令之后最大化流水线的吞吐量 |
| -fguess-branch-probability | 尝试根据启发式方法猜测分支概率,优化分支预测 |
| -fif-conversion 和 -fif-conversion2 | 将条件分支转换为无分支等价物(如条件移动指令)来消除分支 |
| -fmove-loop-invariants | 启用循环不变代码移动优化,将不依赖于循环变量的计算移出循环 |
| -fauto-inc-dec | 在合适的情况下使用自动增量/减量指令优化内存访问 |
| -fshrink-wrap | 优化函数prologue和epilogue,仅在需要时保存/恢复寄存器 |
| -fbranch-count-reg | 使用计数寄存器进行分支操作优化 |
| -fshrink-wrap-separate | 为函数的不同部分分别应用shrink wrapping优化 |
| -fcombine-stack-adjustments | 合并相邻的堆栈调整操作,减少指令数量 |
| -fsplit-wide-types | 将宽类型操作拆分为更小单元的操作,提高寄存器利用率 |
| -fcompare-elim | 消除冗余的比较操作,特别是那些结果已被计算的操作 |
| -fssa-backprop | 在SSA形式中执行反向传播优化,提高常量传播效果 |
| -fcprop-registers | 执行复制传播,用源值替换寄存器的复制操作 |
| -fssa-phiopt | 优化SSA形式中的Phi节点,减少不必要的控制流合并 |
| -ftree-bit-ccp | 基于位级的条件常量传播,在树级别优化常量值 |
| -fdefer-pop | 延迟栈弹出操作,合并多个弹出为单个操作 |
| -ftree-ccp | 在树级别执行条件常量传播,提高优化精度 |
| -ftree-ch | 进行复制传播和死代码消除的组合优化 |
| -ftree-coalesce-vars | 合并生命周期不重叠的变量,减少寄存器压力 |
| -ftree-copy-prop | 在树级别执行复制传播,替代变量引用 |
| -ftree-dominator-opts | 基于支配关系分析进行优化,改进控制流 |
| -ftree-sink | 将计算"下沉"到使用它们的代码块,减少不必要的计算 |
| -ftree-sra | 标量替换聚合(如结构体),用独立标量替换复合数据结构访问 |
| -ftree-ter | 临时表达式替换,消除不必要的临时变量 |
| -fomit-frame-pointer | 在可能的情况下省略帧指针,释放一个额外的通用寄存器 |
| -funit-at-a-time | 一次性处理整个编译单元,允许跨函数优化 |
| -freorder-blocks | 重新排列基本块以提高指令缓存和分支预测效率 |
| -fmerge-constants | 合并相同的常量值,减少内存占用 |
当启用 -O2 时,GCC 启用的额外优化标志
| 优化标志 | 作用说明 |
|---|---|
| -falign-functions, -falign-labels, -falign-loops | 将函数、跳转目标和循环位置的起始地址对齐,以便处理器高效访问内存 |
| -fdelete-null-pointer-checks | 假设解引用空指针是不安全的,移除冗余的空指针检查 |
| -fdevirtualize 和 -fdevirtualize-speculatively | 尽可能将虚函数调用转换为直接调用,提高内联可能性 |
| -fgcse | 全局公共子表达式消除,消除函数中重复的计算 |
| -finline-functions 和 -finline-small-functions | 内联函数调用,特别是小函数,减少调用开销 |
| -findirect-inlining | 内联通过函数指针调用的函数,当目标可确定时 |
| -fcaller-saves | 将需要保存的寄存器保存/恢复延迟到实际需要的点 |
| -fpartial-inlining | 仅内联函数的部分(如快速路径),保留复杂的慢速路径 |
| -fcode-hoisting | 将公共代码提取到控制流图的支配节点,减少代码复制 |
| -fpeephole2 | 指令级别的窥孔优化,替换低效指令序列为高效序列 |
| -fcrossjumping | 识别并合并具有相同结尾的代码路径,减少代码大小 |
| -freorder-blocks-algorithm=stc | 使用更复杂的算法重新排列基本块,优化执行路径 |
| -fcse-follow-jumps 和 -fcse-skip-blocks | 跟随跳转和跳过块进行公共子表达式消除 |
| -freorder-functions | 重新排列函数在二进制文件中的顺序,提高局部性 |
| -fschedule-insns 和 -fschedule-insns2 | 指令调度,重排指令以填充分支延迟槽和减少数据依赖 |
| -fsched-interblock 和 -fsched-spec | 跨基本块调度和推测执行优化,提高处理器流水线效率 |
| -fstrict-aliasing | 假设代码遵守严格的别名规则,允许更激进的优化 |
| -ftree-loop-vectorize | 自动向量化适合的循环,利用SIMD指令提高性能 |
| -ftree-slp-vectorize | 超字级并行向量化,识别并行操作序列进行向量化 |
| -fipa-bit-cp, -fipa-cp, -fipa-vrp | 跨过程的位级常量传播、常量传播和值范围传播 |
| -fipa-icf | 识别并合并功能相同的函数,减少代码大小 |
| -fipa-sra | 跨过程的标量替换聚合,优化参数传递 |
| -fipa-ra | 跨过程寄存器分配优化 |
| -ftree-switch-conversion | 将switch语句转换为查找表或位测试,提高执行效率 |
| -ftree-tail-merge | 合并具有相同尾部代码的路径,减少重复代码 |
| -ftree-vrp | 值范围传播,通过分析变量的可能取值范围优化代码 |
| -fvect-cost-model=very-cheap | 应用宽松的向量化成本模型,更积极地进行向量化 |
| -flra-remat | 重物化(rematerialization)优化,重新计算值而不是从内存加载 |
| -fisolate-erroneous-paths-dereference | 隔离可能导致错误解引用的代码路径,提高安全性 |
| -fhoist-adjacent-loads | 提升相邻的加载操作,合并内存访问 |
| -fthread-jumps | 线程跳转,优化条件跳转链 |
| -frerun-cse-after-loop | 在循环优化后重新运行公共子表达式消除 |
| -fexpensive-optimizations | 启用一系列计算成本高但收益显著的优化 |
| -fstore-merging | 合并相邻的存储操作,减少内存访问 |
| -foptimize-sibling-calls | 优化尾递归和兄弟调用,消除不必要的栈帧 |
| -foptimize-strlen | 专门优化strlen函数调用,特别是在常量字符串上 |
当启用 -O3 时,GCC 启用的额外优化标志
| 优化标志 | 作用说明 |
|---|---|
| -fipa-cp-clone | 创建函数克隆以增强跨程序常量传播,通过复制函数为特定调用上下文定制 |
| -fsplit-loops | 将循环拆分为多个,使每个循环处理更简单的条件,避免循环内分支 |
| -funswitch-loops | 将循环不变的条件分支移出循环,创建多个专用循环版本 |
| -fgcse-after-reload | 在寄存器分配后执行全局公共子表达式消除,捕获新机会 |
| -fsplit-paths | 分裂控制流路径,使优化器能更精确地分析每条路径 |
| -floop-interchange | 交换嵌套循环的顺序,改善缓存局部性和向量化可能性 |
| -ftree-loop-distribution | 将单个循环拆分为多个,每个处理不同数组,改善缓存利用 |
| -floop-unroll-and-jam | 展开外层循环并将内层循环合并,减少循环开销和改善数据局部性 |
| -fpeel-loops | 从循环中"剥离"第一次或最后一次迭代,简化剩余循环体 |
| -fpredictive-commoning | 识别可预测的重复计算,将它们提取到循环外 |
| -fvect-cost-model=dynamic | 使用动态成本模型进行向量化决策,运行时选择最佳策略 |
| -fversion-loops-for-strides | 为不同内存访问模式创建循环版本,优化特定步长的访问 |
| -ftree-partial-pre | 部分部分冗余消除,更精细地平衡代码大小和执行速度 |
GCC不安全快速数学优化标志-ffast-math 及其启用的不安全浮点优化标志
| 优化标志 | 作用说明 | 风险等级 |
|---|---|---|
| -fno-math-errno | 禁止数学函数设置errno,允许编译器优化掉错误检查代码 | 中等 |
| -funsafe-math-optimizations | 允许编译器执行不严格遵守IEEE/ISO标准的浮点优化,如重新关联表达式 | 高 |
| -ffinite-math-only | 假设所有浮点计算只产生有限值(无NaN或无穷大),移除相关检查 | 高 |
| -fno-rounding-math | 禁用对不同舍入模式的支持,假设程序始终使用默认舍入模式 | 中等 |
| -fno-signaling-nans | 假设程序不使用信号NaN(signaling NaNs),只处理静默NaN | 低到中等 |
| -fcx-limited-range | 优化复数乘法和除法,假设中间计算不会溢出 | 中等 |
| -fexcess-precision=fast | 允许使用处理器提供的额外精度进行中间计算,但可能导致跨平台结果不一致 | 中等到高 |
这些优化标志不会在任何标准优化级别(-O1、-O2、-O3)中自动启用
使用建议:
- 仅在经过彻底测试后,确认应用对精度损失不敏感时使用
- 建议通过子标志启用而非-ffast-math全局启用
GCC较新版本支持的对低延迟优化有作用的编译选项
GCC较新版本支持的对低延迟优化有作用的编译选项
| 编译选项 | -O2 |
-O3 |
说明 |
|---|---|---|---|
-finline-functions |
❌ | ✅ | -O3会启用更激进的函数内联,包括非 inline函数;-O2仅内联 inline标记或小函数。 |
-floop-interchange |
✅ | ✅ | 属于循环优化的一部分,用于改善内存访问模式,在 -O2及以上启用。 |
-funswitch-loops |
❌ | ✅ | 这是一个较激进的优化,将循环内的 if提出,生成多个版本循环。仅在 -O3中默认启用。 |
-ftree-loop-distribution |
✅ | ✅ | 在 -O2中已启用,用于拆分循环以便后续优化(如向量化)。 |
-ftree-loop-distribute-patterns |
✅ | ✅ | 识别常见模式(如 memset、memcpy),通常在 -O2起作用,-O3更积极。 |
-ftree-loop-vectorize |
✅ | ✅ | 循环向量化在 -O2和 -O3都启用,但 -O3更激进。 |
-ftree-slp-vectorize |
❌ | ✅ | SLP 向量化(基本块级向量化)默认只在 -O3启用。 |
-floop-unroll-and-jam |
❌ | ✅ | 展开外层并合并内层,提高数据局部性,是 -O3特有的优化。 |
-fipa-cp-clone |
✅ | ✅ | 属于 IPA(过程间分析)优化,在 -O2 和 -O3中启用,但 -O3 更激进。 |
提供编译器提示¶
-
在某些情况下,编译器无法自行证明某个优化是安全的或有益的。通过编译器提示来提供额外信息,辅助编译器做出决策。
-
__restrict__关键字(C/C++): 用于告诉编译器指针指向的内存区域不会重叠(别名问题),这使得编译器可以放心地进行循环向量化和代码移动等优化,而无需插入昂贵的运行时别名检查。 #pragma unroll(N)/#pragma clang loop vectorize(enable): 强制编译器对循环进行展开或向量化,即使其成本模型认为这样做无益。这在特定场景下,人工判断比编译器更准确时非常有用。[[likely]]/[[unlikely]]属性(C++20): 用于提示编译器条件分支的预测倾向。编译器会根据这些提示优化机器代码布局,将最常执行的代码路径放在一起,从而减少分支预测错误的惩罚。
使用编译器内建函数¶
gcc(g++)提供了很多类型的内建函数,一般都是优化良好的对应功能的实现,在有些场景需要自己实现更好的版本替代。
-
标准库替代函数
-
__builtin_memcpy、__builtin_memset、__builtin_strlen、__builtin_strcpy -
算术与位操作函数
-
__builtin_add_overflow、__builtin_clz、__builtin_ctz、__builtin_popcount -
类型检查与转换函数
-
__builtin_types_compatible_p(检查类型兼容性)、__builtin_choose_expr(编译期条件选择表达式) -
程序流程控制函数
-
__builtin_unreachable(标记不可达代码,辅助优化)、__builtin_return_address(获取返回地址)、__builtin_expect -
内存与指针操作函数
-
__builtin_alloca(栈上动态分配内存);__builtin_assume_aligned(告知编译器指针对齐方式) -
硬件相关函数
-
x86的
__builtin_ia32_mfence(内存屏障)、ARM的__builtin_arm_ldrexd(加载-排他指令) -
诊断与调试函数
-
示例:
__builtin_trap(触发陷阱指令,用于异常处理);__builtin___func__(获取当前函数名) -
缓存操作相关函数
-
预加载函数:
__builtin_prefetch(提前将数据加载到缓存) -
缓存刷新/失效:
__builtin_clear_cache(清理指定范围缓存);x86架构的__builtin_ia32_clflush(缓存行刷新) -
向量化相关函数
-
自动向量化辅助:
__builtin_assume_aligned(指定对齐方式,辅助编译器生成向量指令) -
显式向量操作:如x86的
__builtin_ia32_addps(AVX/SSE向量加法)、ARM的__builtin_neon_vadds8(NEON向量操作) -
其他实用内建函数
-
__builtin_parity(x)(GCC):统计x中1的个数的奇偶性(奇数返回1,偶数返回0),用于错误检测(如校验和);
检查编译器生成的代码¶
1. 编译器选项
使用命令行选项 -S (GCC/Clang) 或 /Fa (MSVC)
使用完整优化选项(如 -O2, -O3)生成有代表性的代码
Intel 编译器的源代码注释选项(/FAs 或 -fsource-asm)会禁用某些优化
2. 调试器方法
调试模式不包含完整优化
优化后的发布版本可能缺少行号信息
- 在代码中插入硬断点:
__asm int 3;或__debugbreak(); - 单步执行优化代码
- 重要:完成分析后移除断点
3. 反汇编工具
当编译器不提供汇编输出时,使用反汇编器(如 objconv)
对目标文件进行反汇编分析
其他与优化相关的内容¶
与优化相关的命令行选项
| 优化类型 | MS编译器 (Windows) | Gnu和Clang编译器 | Intel编译器 (Windows) | Intel编译器 (Linux) |
|---|---|---|---|---|
| 速度优化 | /O2 or /Ox | -O3 or -Ofast | /O3 | -O3 |
| 过程间优化 | /Og | |||
| 全程序优化 | /GL | --combine -fwhole-program | /Qipo | -ipo |
| 无异常处理 | /EHs- | |||
| 无F.P.异常捕获 | /fp:except- | -fno-trapping-math -fno-math-errno | ||
| 无栈帧 | /Oy | -fomit-framepointer -fomit-framepointer | ||
| 无RTTI | /GR- | -fno-rtti | /GR- | -fno-rtti |
| 假设无指针别名 | /Oa | -fno-alias | ||
| 非严格浮点 | /fp:fast | -ffast-math | /fp:fast /fp:fast=2 | -fp-model fast, -fp-model fast=2 |
| 融合乘加 | -ffp-contract=fast | |||
| 除以常数=乘以倒数 | -freciprocal-math | |||
| 假设无溢出或NAN | -ffinite-math-only | |||
| 忽略零的符号 | -fno-signed-zeros | |||
| 简单成员指针 | /vms | -fno-complete-member-pointers | /vms | |
| 快速调用函数 | /Gr | |||
| 向量调用函数 | /Gv | |||
| 函数级链接 | /Gy | -ffunction-sections | /Gy | -ffunction-sections |
| SSE指令集(128位浮点向量) | /arch:SSE | -msse | /arch:SSE | -msse |
| SSE2指令集(128位整数或双精度) | /arch:SSE2 | -msse2 | /arch:SSE2 | -msse2 |
| SSE3指令集 | -msse3 | /arch:SSE3 | -msse3 | |
| 补充SSE3指令集 | -mssse3 | /arch:SSSE3 | -mssse3 | |
| SSE4.1指令集 | -msse4.1 | /arch:SSE4.1 | -msse4.1 | |
| AVX指令集 | /arch:AVX | -mAVX | /arch:AVX | -mAVX |
| AVX2指令集 | /arch:AVX2 | -mAVX2 | /arch:AVX | -mAVX2 |
| AVX512F指令集 | -mavx512f | |||
| AVX512VL/BW/DQ指令集 | /arch:AVX512 | -mavx512vl -mavx512bw -mavx512dq | /arch:COREAVX512 | -mavx512vl -mavx512bw -mavx512dq |
| 自动向量化 | /fp:fast /fp:except- | -O2 -fno-trapping-math -fno-math-errno fveclib=libmvec | ||
| 多线程自动并行化 | /Qparallel | -parallel | ||
| OpenMP指令并行化 | /openmp | -fopenmp | /Qopenmp | -openmp |
| 32位代码 | -m32 | |||
| 64位代码 | -m64 | |||
| 静态链接(多线程) | /MT | -static | /MT | -static |
| 生成汇编清单 | /FA | -S -masm=intel | /FA | -S |
| 生成映射文件 | /Fm | |||
| 生成优化报告 | /Qopt-report | -opt-report |
与优化相关的编译器指令和关键字
| 优化类型 | MS编译器 | Gnu和Clang编译器 | Intel编译器 (Windows) | Intel编译器 (Linux) |
|---|---|---|---|---|
| 16字节对齐 | __declspec(align(16)) | __attribute((aligned(16))) | __declspec(align(16)) | __attribute((aligned(16))) |
| 16字节对齐 (C++11) | alignas(16) | alignas(16) | alignas(16) | alignas(16) |
| 假设指针对齐 | #pragma vector aligned | #pragma vector aligned | ||
| 假设指针不别名 | #pragma optimize("a", on) __restrict | __restrict | __declspec(noalias)__restrict #pragma ivdep | __restrict #pragma ivdep |
| 假设函数是纯函数 | __attribute((const)) | __attribute((const)) | ||
| 假设函数不抛出异常 | throw() | throw() | throw() | throw() |
| 假设函数仅从同一模块调用 | static | static | static | static |
| 假设成员函数仅从同一模块调用 | attribute((visibility("internal"))) | attribute((visibility("internal"))) | ||
| 向量化 | #pragma vector always | #pragma vector always | ||
| 优化函数 | #pragma optimize(...) | |||
| 快速调用函数 | __fastcall | __attribute((fastcall)) | __fastcall | |
| 向量调用函数 | __vectorcall | __vectorcall (Clang only) | __vectorcall | |
| 非缓存写入 | #pragma vector nontemporal | #pragma vector nontemporal |
预定义宏
| 识别类型 | MS编译器 (Windows) | Gnu编译器 | Clang编译器 | Intel编译器 (Windows) | Intel编译器 (Linux) |
|---|---|---|---|---|---|
| 编译器标识 | _MSC_VER and not __INTEL_COMPILER | GNUC and not __INTEL_COMPILER and not clang | clang | __INTEL_COMPILER or__INTEL_LLVM_COMPILER | __INTEL_COMPILER or__INTEL_LLVM_COMPILER |
| 16位平台 | not _WIN32 | n.a. | n.a. | n.a. | n.a. |
| 32位平台 | not _WIN64 | not _WIN64 | |||
| 64位平台 | _WIN64 | _LP64 | _LP64 | _WIN64 | _LP64 |
| Windows平台 | _WIN32 | _WIN32 | |||
| Linux平台 | n.a. | __unix__ / __linux__ |
__unix__ / __linux__ |
__unix__ / __linux__ |
__unix__ / __linux__ |
| x86平台 | _M_IX86 | _M_IX86 | |||
| x86-64平台 | _M_IX86 and _WIN64 | _M_X64 | _M_X64 |
静态链接与动态链接¶
动态库加载工具
#pragma once
#include <dlfcn.h>
#include <string>
/**
* @brief Linux平台动态库加载辅助类
*
* 动态库加载、符号解析和释放功能的封装接口。
*/
class library_helper {
public:
/// 动态库句柄类型定义
using dll_handle = void*;
/// 符号句柄类型定义
using process_handle = void*;
/**
* @brief 加载动态库
*
* @param filename 动态库文件路径
* @param debug_suffix 调试版本后缀(仅在调试模式下使用)
* @return dll_handle 加载库的句柄,失败时返回nullptr
*
* @note 在调试模式下(未定义NDEBUG),会自动为文件名添加debug_suffix后缀
* @note 使用RTLD_NOW模式加载,立即解析所有未定义符号,便于及早发现错误
* @note 出于性能考虑,函数内部不打印错误信息,错误处理由调用方负责
*/
static dll_handle load_library(const char* filename,
const char* debug_suffix = nullptr) noexcept {
#ifndef NDEBUG
std::string dll_name = debug_suffix ? std::string(filename) + debug_suffix
: std::string(filename);
#else
const std::string& dll_name = filename;
#endif
// 使用RTLD_NOW立即解析所有未定义符号,便于及早发现错误
dll_handle ret = ::dlopen(dll_name.c_str(), RTLD_NOW);
// 在性能关键代码中,避免打印到stdout以减少I/O开销
// 如果需要错误日志,应在应用层面处理
return ret;
}
/**
* @brief 释放已加载的动态库
*
* @param handle 要释放的库句柄
*
* @note 函数会检查句柄是否为空,避免无效释放
* @note 使用likely属性提示编译器优化分支预测
*/
static void free_library(dll_handle handle) noexcept {
if (handle != nullptr) [[likely]] {
::dlclose(handle);
}
}
/**
* @brief 从已加载的库中获取符号
*
* @param handle 库句柄
* @param name 符号名称
* @return process_handle 符号指针,未找到时返回nullptr
*
* @note 函数会检查句柄是否为空,避免无效操作
* @note 使用likely属性提示编译器优化分支预测
*/
static process_handle get_symbol(dll_handle handle,
const char* name) noexcept {
if (handle != nullptr) [[likely]] {
return ::dlsym(handle, name);
}
return nullptr;
}
};
在超低延迟的场景中其实更多是需要将所有依赖库静态链接,以最小化运行时性能延迟,不需要在运行时启动时查找和解析依赖项,同时为在编译和链接时进行超级优化创造机会,但是部分交易系统架构为了做策略管理和保密工作,仍会使用动态链接。
编译器动态链接选项优化
32位Linux
- 默认:使用
-fpic编译,包含PLT/GOT - 优化:使用
-fno-pic编译 - 优点:移除GOT/PLT查找,代码更快
- 缺点:无法共享代码段(每个进程需单独代码实例)
- 推荐:若不需要符号覆盖,使用此选项
64位Linux
- 推荐:使用
-fpie代替-fpic - 优点:生成相对地址,不为内部引用使用GOT/PLT
- 缺点:不能有公共变量
- 解决:将全局变量、内部函数标记为
static或__attribute__((visibility("hidden"))) - 避免:
-mcmodel=large(使用64位地址,效率低)