编译标志与编译目标

从编译器获得高性能的第一步是要求它这么做,这是通过一百多个不同的编译器选项、属性和杂注来完成的。

优化等级

GCC中的速度优化主要有四个半级别:

  • -O0是默认选项,不进行优化(尽管从某种意义上说,它确实在编译时间上做了优化)。
  • -O1(别名为-O)做了一些“唾手可得”的优化,且几乎不影响编译时间。
  • -O2使能所有已知几乎没有负面副作用并且编译时间时间相对合理的优化(这是大多数项目用于生产构建的选项)。
  • -O3会做非常激进的优化,几乎使能所有GCC中实现的正确的优化。
  • -Ofast-O3优化的基础上,再加上一些优化标志,这些标志可能会破坏严格的标准规定,但对大多数应用程序并不至关重要(例如,浮点运算可能会被重新排列,从而使结果在尾数中偏离几位)。

还有许多优化标志甚至没有包含在-Ofast中,因为它们只针对具体场景,默认情况下启用它们更有可能损害性能,而不是提高性能,我们将在下一节中讨论其中的一部分。

指定目标平台

下一件你可能想做的事是告诉编译器更多关于这个代码应该在哪些计算机平台上运行的信息:平台集范围越小,优化效果越好。默认情况下,编译器将生成可以在任何相对较新( > 2000)的x86 CPU上运行的二进制文件。缩小范围的最简单方法是传递-march标志来指定确切的微体系结构(microarchitecture):例如-march=haswell。如果你在执行二进制文件的同一台计算机上进行编译,则可以使用-march=native进行自动检测。

指令集通常是向后兼容的,因此通常指定需要支持的最老的微体系结构的名称就足够了。一种更稳健的方法是列出CPU保证具有的特定特性:如-mavx2-mpopcnt。当你只想为特定机器调整程序,而不使用任何可能使其在不兼容的CPU上崩溃的指令时,可以使用-mtune标志(默认情况下,-march=x也意味着-mtune=x)。

也可以用pragmas代替编译标志,为编译单元指定编译选项:

#pragma GCC optimize("O3")
#pragma GCC target("avx2")

当你想要在不增加整个项目的构建时间,只优化单个高性能过程时,这非常有用。

多版本函数

有时,你可能还想在单个库中提供几个特定架构的实现。你可以使用基于属性的语法在编译时自动在多版本函数之间进行选择:

__attribute__(( target("default") )) // fallback implementation
int popcnt(int x) {
int s = 0;
for (int i = 0; i < 32; i++)
s += (x>>i&1);
return s;
}

__attribute__(( target("popcnt") )) // used if popcnt flag is enabled
int popcnt(int x) {
return __builtin_popcount(x);
}

在Clang中,不能在源代码中使用pragmas设置目标和优化标志,但能以与GCC中相同的方式使用属性。