跳转至

利用编译器等优化程序

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"表示过度优化(如过度循环展开)。

调整编译器选项

  1. 最基本也是最重要的一步是启用编译器的优化标志。通常,以下几个是必备的:

  2. -O3: 启用与机器无关的激进优化。这是大多数编译器提供的最高级别优化之一,会进行大量的代码转换,如循环展开、函数内联、死代码消除等。

  3. -march=native 或 -march=<CPU_ARCH>: 启用针对特定 CPU 架构的优化。这会指示编译器生成针对当前机器或指定架构(如 core-avx2、arm64)的指令集优化代码,从而利用 CPU 特定的高性能指令(如 AVX、AVX2、AVX-512、NEON)。
  4. -flto(Link Time Optimization)/ -fipo(Inter-Procedural Optimization): 启用链接时优化(LTO),也称为过程间优化(IPO)。通常,编译器一次只能优化一个编译单元(源文件)。LTO 允许编译器在链接阶段对整个程序进行全局分析和优化,从而发现跨文件边界的优化机会,如更激进的函数内联、死代码消除和数据流优化。
  5. -fwhole-program 选项启用 WPO 以启用过程间优化,将整个代码库视为一个整体程序,与 LTO 特性类似。
  6. -fprofile-generate: 在编译时插入性能探针,用于记录程序运行时的执行频率(如函数调用次数、分支跳转次数等)。-fprofile-use: 编译器利用带探针的程序执行收集到的热点数据,对频繁执行的代码路径进行更激进的优化,对冷路径则可能减少优化开销。

  7. 其他重要选项: 还有许多其他选项会影响性能,例如:

  8. -ffast-math: 允许编译器重新关联浮点运算,可能改变运算顺序,从而启用更多优化(如浮点运算的向量化)。但需谨慎使用,因为它可能导致结果的微小不精确,并涉及 NaN、无穷大等特殊行为。在数据库中,这通常只在对数值精度要求不那么严格的分析型查询中使用。

  9. -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)中自动启用

使用建议:

  1. 仅在经过彻底测试后,确认应用对精度损失不敏感时使用
  2. 建议通过子标志启用而非-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 更激进。

提供编译器提示

  1. 在某些情况下,编译器无法自行证明某个优化是安全的或有益的。通过编译器提示来提供额外信息,辅助编译器做出决策。

  2. __restrict__ 关键字(C/C++): 用于告诉编译器指针指向的内存区域不会重叠(别名问题),这使得编译器可以放心地进行循环向量化和代码移动等优化,而无需插入昂贵的运行时别名检查。

  3. #pragma unroll(N) / #pragma clang loop vectorize(enable): 强制编译器对循环进行展开或向量化,即使其成本模型认为这样做无益。这在特定场景下,人工判断比编译器更准确时非常有用。
  4. [[likely]] / [[unlikely]] 属性(C++20): 用于提示编译器条件分支的预测倾向。编译器会根据这些提示优化机器代码布局,将最常执行的代码路径放在一起,从而减少分支预测错误的惩罚。

使用编译器内建函数

gcc(g++)提供了很多类型的内建函数,一般都是优化良好的对应功能的实现,在有些场景需要自己实现更好的版本替代。

  1. 标准库替代函数

  2. __builtin_memcpy、__builtin_memset、__builtin_strlen、__builtin_strcpy

  3. 算术与位操作函数

  4. __builtin_add_overflow、__builtin_clz、__builtin_ctz、__builtin_popcount

  5. 类型检查与转换函数

  6. __builtin_types_compatible_p(检查类型兼容性)、__builtin_choose_expr(编译期条件选择表达式)

  7. 程序流程控制函数

  8. __builtin_unreachable(标记不可达代码,辅助优化)、__builtin_return_address(获取返回地址)、__builtin_expect

  9. 内存与指针操作函数

  10. __builtin_alloca(栈上动态分配内存);__builtin_assume_aligned(告知编译器指针对齐方式)

  11. 硬件相关函数

  12. x86的__builtin_ia32_mfence(内存屏障)、ARM的__builtin_arm_ldrexd(加载-排他指令)

  13. 诊断与调试函数

  14. 示例:__builtin_trap(触发陷阱指令,用于异常处理);__builtin___func__(获取当前函数名)

  15. 缓存操作相关函数

  16. 预加载函数:__builtin_prefetch(提前将数据加载到缓存)

  17. 缓存刷新/失效:__builtin_clear_cache(清理指定范围缓存);x86架构的__builtin_ia32_clflush(缓存行刷新)

  18. 向量化相关函数

  19. 自动向量化辅助:__builtin_assume_aligned(指定对齐方式,辅助编译器生成向量指令)

  20. 显式向量操作:如x86的__builtin_ia32_addps(AVX/SSE向量加法)、ARM的__builtin_neon_vadds8(NEON向量操作)

  21. 其他实用内建函数

  22. __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位地址,效率低)