我正在寻找特定代码的最佳优化标志。通过谷歌搜索一段时间,我发现选择最佳优化没有黄金法则。 答案取决于具体的代码,编译器和机器。
推荐的优化标志是-O2
,尽管在某些情况下-Os
(为了生成更小的二进制文件)可以生成具有更快执行速度的二进制文件。我更喜欢忽略-O3
的使用和优越的优化,因为在某些情况下它可能是危险的。在某些情况下,将-O2
与-Os
标记组合可以产生更好的结果。或者在其他情况下,与-march=native
的编译为特定机器提供了优化的二进制文件(因此它可以生成具有较小执行时间的二进制文件)。
对于我的特定代码(使用valgrind --tool=calgrind
和perf stat
),我发现-march=native
没有产生最短的运行时间。
现在,我的问题是:
如果对于我的特定代码,我发现使用
-Os
和/或-O2
生成最佳二进制文件(我的意思是生成更快执行时间的二进制文件),这在其他机器中是最佳的?
我想在一台计算机中确定最佳优化标志,但我必须在不同的计算机上运行(某些人使用MacOS,其他人使用Linux,所有这些计算机的操作系统版本都不同)。
提前感谢任何建议或想法。
答案 0 :(得分:4)
TL:DR :不,不同的CPU就像不同的东西。如果编译器只能无效地进行自动向量化,那么在一台机器上可能会获胜,但在另一台机器上却会失败。
gcc -O2 -march=native
或gcc -O3 -march=native
是不错的选择。或者更好的是,那些+链接时优化和/或配置文件引导优化,因此编译器知道哪些循环是热的,哪些分支通常只有一种方式而哪些分支是不可预测的。
IDK,如果您曾尝试使用gcc -march=native
而没有-O
选项,但这不会有用; <{1}}的默认-march=native
仍然是垃圾。
-O0
值得一试。 clang man-page表示它有时会使代码更大,所以要小心它,并进行基准测试以确保代码真的更快。就性能而言,这只是一种“风险”,而不是正确性。如果没有其他选项专门启用不安全的优化,编译器将不会弯曲语言规则。
Clang docs说-O3
目前相当于-O4
至少对于gcc,仅在-O3
启用自动向量化。 Clang可能还有其他好东西,只发生在-O3
。
我不确定“一般用途”建议是否仍然只有-O3
,或者-O2
是否通常保守到足以一直使用。对于-O3
/ -fprofile-generate
,编译器应该避免为不常运行的路径膨胀代码大小,并且只展开实际上很热的循环。
如果-fprofile-use
显示任何I-cache未命中,则perf
值得尝试。大多数源文件可能-Os
,而最热门函数的源文件可能-Os
。
-O3
会进行一些循环展开,但clang -O3
不会在没有gcc -O3
(或-fprofile-use
的情况下)启用循环展开。
还有-funroll-loops
,它可以实现潜在的不安全优化。如果您的代码仍能正常运行,那就去吧。 (我认为-Ofast
主要意味着可能会有不同的溢出。对于FP代码,如果你不关心你的代码如果有NaN / Inf,或者关于确切的操作顺序,那么你可以使用{{1 (或只是unsafe
)。
我要测试的内容列表绝对包括,如果我想花一些时间找到最佳选项来编译我将花费大量CPU时间运行的东西:
-Ofast
-O3 -ffast-math
(clang -O3 -march=native
之后)clang -O3 -march=native -fprofile-use
(链接时优化,用于整个程序优化:在源文件之间内联函数(或者至少看看它们是否有副作用,甚至如果没有内联)。)gcc也有... -fprofile-generate
。-flto -emit-llvm
-flto
或甚至gcc -O3 -march=native -fno-stack-protector -fprofile-use
我猜-ffast-math
也值得一试。即使I-cache / uop-cache未命中也不成问题,对齐的怪癖也会有所帮助。
如果-Ofast
不是编译器中的默认值,那么也可以使用它来释放额外的整数寄存器并缩短函数序言/结尾。
如果要在所有计算机上使用相同的二进制文件,请-Os
。 (假设clang分享了gcc的曲目/曲调选项。)
或只是-fomit-frame-pointer
或其他什么。只要你正在构建一个x86-64二进制文件,我认为只有新的SSE指令才对编译器感兴趣。 (不是popcnt,BMI等)
另一种方法是在每台机器上的主目录中进行源检查,并使用本地编译器构建程序。但是如果你在一台机器上有一个非常新版本的gcc或clang或者intel的编译器,那么只使用它就有意义了。
您可能还会考虑自动并行化:-march=some_baseline -mtune=something
,其中-msse4.2 -mtune=sandybridge
是要使用的线程数。
对所有这些的警告是,我听说过gcc -ftree-parallelize-loops=n
代码中断,因为它取决于语言规则不需要的行为。因此,积极的优化找到了一种优化方式,使代码不再做同样的事情。如果您希望代码快速运行,请确保避免未定义的行为,以便可以将优化器一直调高。 (IIRC,最近有一个问题是关于检查编译器何时根据假设某些东西进行优化,因为否则会发生未定义的行为。)
答案 1 :(得分:1)
你实际上是指着自己的答案:
建议的优化标志是-O2,但在某些情况下-Os
我们只需要补充一点,这种变化不仅仅是由于源代码(一个代码库为-Os提供了更好的结果,另一个代码提供了更好的-O2结果),但是代码运行的机器上。
想象一下相同指令集的不同处理器(无需重新编译)。一个人可能有一个小缓存,-Os会产生较小的可执行文件,避免大量缓存未命中,可能会破坏-O2的性能。第二个处理器作为一个巨大的缓存,所以当代码用-O2编译时它没有那么多缓存未命中,然后允许代码在-O2情况下运行得更快。
这当然只是一个天真简化的例子,我想现实世界中的参数组合相当令人生畏。但是它给你一个暗示,为什么很难提前确定最佳编译。
一些项目正在做的是:它们使用不同的目标二进制大小和指令集扩展进行编译,然后尝试确定在应用程序加载时要运行的实际二进制文件(首先在执行平台属性中进行操作,以使一个有根据的猜测。)