GCC/Clang。如果某些 -O 标志在某台机器上是最优的,那么它们在另一台机器上也会是最优的吗?
GCC/Clang. If some -O flags are optimal in a certain machine, they will be also optimal in another machine?
我正在为我的特定代码寻找最佳优化标志。通过谷歌搜索一段时间,我发现没有选择最佳优化的黄金法则。 答案取决于具体的代码、编译器和机器。
推荐的优化标志是 -O2
,尽管在某些情况下 -Os
(为了生成更小的二进制文件)可以生成执行速度更快的二进制文件。我宁愿忽略 -O3
和高级优化的使用,因为在某些情况下它可能很危险。在某些情况下,将 -O2
与 -Os
标志结合使用会产生更好的结果。或者在其他情况下,使用 -march=native
的编译会为特定机器提供优化的二进制文件(因此它可以生成执行时间更短的二进制文件)。
对于我的特定代码(使用 valgrind --tool=calgrind
和 perf stat
),我发现 -march=native
不会生成最小的 运行ning 时间。
现在,我的问题是:
if for my specific code I have found that the optimal binary (I mean, binary which produces faster execution time) is generated using -Os
and/or -O2
, this will be optimal in other machines?.
我只想在一台计算机上确定最佳优化标志,但我必须在不同的计算机上 运行(有人使用 MacOS,其他人使用 Linux,以及他们都有 OS-版本不同)。
提前感谢您的任何建议或想法。
你其实是在指着自己的答案:
The recommended optimization flag is -O2, although in certain cases -Os
我们只需要补充一点,这种变化不仅是由于源代码(一个代码库使用 -[=21= 提供更好的结果,另一个使用 -O2 提供更好的结果),而是由于非常机器的代码是 运行ning on.
想象一下不同的处理器用于相同的指令集(无需重新编译)。一个人可以有一个小的缓存,一个 -Os 然后会产生更小的可执行文件,避免大量缓存未命中,这可能会破坏 -O2 的性能。第二个处理器作为一个巨大的缓存,所以当使用 -O2 编译代码时它不会有那么多的缓存未命中,然后允许代码在 -O2 的情况下 运行 更快。
这当然只是一个天真的简化示例,我想现实世界中的参数组合相当令人生畏。但它给了你一个提示,为什么很难提前确定最佳编译。
一些项目正在做的是:它们使用不同的目标二进制大小和指令集扩展进行编译,然后在应用程序加载时尝试确定要 运行 哪个实际二进制文件(首先在执行平台属性,做出有根据的猜测)。
TL:DR: 不,不同的 CPU 喜欢不同的东西。自动矢量化在一台机器上可能是赢家,但在另一台机器上可能是输家,如果编译器只能低效地做到这一点。
gcc -O2 -march=native
或 gcc -O3 -march=native
是不错的选择。或者更好的是,那些 + link-time 优化 and/or profile-guided 优化,这样编译器就知道哪些循环是热的,哪些分支通常只走一条路,哪些分支是不可预测的。
IDK 如果您曾经尝试过 gcc -march=native
而没有 -O
选项,但那将没有用; -march=native
与默认 -O0
仍然是垃圾。
-O3
值得一试。 clang 手册页说它有时会使代码更大,所以要小心,并进行基准测试以确保您的代码确实更快。就性能而言,它只是 "risk",而不是正确性。如果没有其他选项专门启用不安全的优化,编译器不会改变语言规则。
Clang 文档说 -O4
目前等同于 -O3
至少对于 gcc,自动矢量化仅在 -O3
启用。 Clang 可能还有其他只发生在 -O3
.
的好东西
我不确定 "general use" 的建议是否仍然只是 -O2
,或者 -O3
是否足够保守,可以一直使用。使用 -fprofile-generate
/ -fprofile-use
,编译器应该避免膨胀不常出现的 运行 路径的代码大小,并且只展开实际上很热的循环。
如果 perf
显示任何 I-cache 未命中,那么 -Os
值得尝试。也许 -Os
用于大多数源文件,而 -O3
用于具有最热门功能的源文件。
clang -O3
进行了一些循环展开,但是 gcc -O3
在没有 -fprofile-use
(当然是 -funroll-loops
)的情况下不会启用循环展开。
还有 -Ofast
,这会启用潜在的不安全优化。如果您的代码仍然可以正常工作,那就去吧。 (我认为 unsafe
主要意味着可能会以不同方式溢出。对于 FP 代码,如果您不关心代码在存在 NaN / Inf 时的行为方式,或者不关心确切的操作顺序,那么您可以使用 -Ofast
(或者只是 -O3 -ffast-math
)。
如果我想花一些时间寻找最佳选项来编译我将花费大量 CPU 时间 运行 的东西,我的测试清单绝对会包括:
clang -O3 -march=native
clang -O3 -march=native -fprofile-use
(在... -fprofile-generate
之后)
- 上面的
-flto -emit-llvm
(link-time optimization, for whole-program optimization: inlineing functions between source files (or at least seeing if它们是否有副作用,即使没有内联。)gcc 也有 -flto
。
gcc -O3 -march=native -fno-stack-protector -fprofile-use
- 以上
-ffast-math
甚至 -Ofast
我想 -Os
也值得一试。对齐的怪癖可以提供帮助,即使 I-cache / uop-cache 未命中不是问题。
如果 -fomit-frame-pointer
不是编译器的默认值,也可以使用它来释放额外的整数寄存器并缩短函数 prologue/epilogue.
如果您想在所有机器上使用相同的二进制文件,那么 -march=some_baseline -mtune=something
。 (假设 clang 共享 gcc 的 arch/tune 选项。)
或者只是 -msse4.2 -mtune=sandybridge
之类的。我认为,只要您正在构建 x86-64 二进制文件,编译器就只会对新的 SSE 指令感兴趣。 (不是 popcnt、BMI 等)
另一种方法是在每台机器上的主目录中进行源代码检出,并使用本地编译器构建程序。但是如果你在一台机器上有一个真正新版本的 gcc 或 clang,或英特尔的编译器,那么使用它是有意义的。
您还可以查看自动并行化:gcc -ftree-parallelize-loops=n
,其中 n
是要使用的线程数。
所有这一切的警告 是我听说过使用 -O3
破坏代码,因为它依赖于语言规则不需要的行为。如此激进的优化找到了一种优化方法,使代码不再做同样的事情。如果您希望您的代码 运行 快速,请确保避免未定义的行为,以便您可以将优化器一直调高。 (IIRC,最近有一个关于检查编译器何时基于假设优化某些东西的问题,因为否则会发生未定义的行为。)
我正在为我的特定代码寻找最佳优化标志。通过谷歌搜索一段时间,我发现没有选择最佳优化的黄金法则。 答案取决于具体的代码、编译器和机器。
推荐的优化标志是 -O2
,尽管在某些情况下 -Os
(为了生成更小的二进制文件)可以生成执行速度更快的二进制文件。我宁愿忽略 -O3
和高级优化的使用,因为在某些情况下它可能很危险。在某些情况下,将 -O2
与 -Os
标志结合使用会产生更好的结果。或者在其他情况下,使用 -march=native
的编译会为特定机器提供优化的二进制文件(因此它可以生成执行时间更短的二进制文件)。
对于我的特定代码(使用 valgrind --tool=calgrind
和 perf stat
),我发现 -march=native
不会生成最小的 运行ning 时间。
现在,我的问题是:
if for my specific code I have found that the optimal binary (I mean, binary which produces faster execution time) is generated using
-Os
and/or-O2
, this will be optimal in other machines?.
我只想在一台计算机上确定最佳优化标志,但我必须在不同的计算机上 运行(有人使用 MacOS,其他人使用 Linux,以及他们都有 OS-版本不同)。
提前感谢您的任何建议或想法。
你其实是在指着自己的答案:
The recommended optimization flag is -O2, although in certain cases -Os
我们只需要补充一点,这种变化不仅是由于源代码(一个代码库使用 -[=21= 提供更好的结果,另一个使用 -O2 提供更好的结果),而是由于非常机器的代码是 运行ning on.
想象一下不同的处理器用于相同的指令集(无需重新编译)。一个人可以有一个小的缓存,一个 -Os 然后会产生更小的可执行文件,避免大量缓存未命中,这可能会破坏 -O2 的性能。第二个处理器作为一个巨大的缓存,所以当使用 -O2 编译代码时它不会有那么多的缓存未命中,然后允许代码在 -O2 的情况下 运行 更快。
这当然只是一个天真的简化示例,我想现实世界中的参数组合相当令人生畏。但它给了你一个提示,为什么很难提前确定最佳编译。
一些项目正在做的是:它们使用不同的目标二进制大小和指令集扩展进行编译,然后在应用程序加载时尝试确定要 运行 哪个实际二进制文件(首先在执行平台属性,做出有根据的猜测)。
TL:DR: 不,不同的 CPU 喜欢不同的东西。自动矢量化在一台机器上可能是赢家,但在另一台机器上可能是输家,如果编译器只能低效地做到这一点。
gcc -O2 -march=native
或 gcc -O3 -march=native
是不错的选择。或者更好的是,那些 + link-time 优化 and/or profile-guided 优化,这样编译器就知道哪些循环是热的,哪些分支通常只走一条路,哪些分支是不可预测的。
IDK 如果您曾经尝试过 gcc -march=native
而没有 -O
选项,但那将没有用; -march=native
与默认 -O0
仍然是垃圾。
-O3
值得一试。 clang 手册页说它有时会使代码更大,所以要小心,并进行基准测试以确保您的代码确实更快。就性能而言,它只是 "risk",而不是正确性。如果没有其他选项专门启用不安全的优化,编译器不会改变语言规则。
Clang 文档说 -O4
目前等同于 -O3
至少对于 gcc,自动矢量化仅在 -O3
启用。 Clang 可能还有其他只发生在 -O3
.
我不确定 "general use" 的建议是否仍然只是 -O2
,或者 -O3
是否足够保守,可以一直使用。使用 -fprofile-generate
/ -fprofile-use
,编译器应该避免膨胀不常出现的 运行 路径的代码大小,并且只展开实际上很热的循环。
如果 perf
显示任何 I-cache 未命中,那么 -Os
值得尝试。也许 -Os
用于大多数源文件,而 -O3
用于具有最热门功能的源文件。
clang -O3
进行了一些循环展开,但是 gcc -O3
在没有 -fprofile-use
(当然是 -funroll-loops
)的情况下不会启用循环展开。
还有 -Ofast
,这会启用潜在的不安全优化。如果您的代码仍然可以正常工作,那就去吧。 (我认为 unsafe
主要意味着可能会以不同方式溢出。对于 FP 代码,如果您不关心代码在存在 NaN / Inf 时的行为方式,或者不关心确切的操作顺序,那么您可以使用 -Ofast
(或者只是 -O3 -ffast-math
)。
如果我想花一些时间寻找最佳选项来编译我将花费大量 CPU 时间 运行 的东西,我的测试清单绝对会包括:
clang -O3 -march=native
clang -O3 -march=native -fprofile-use
(在... -fprofile-generate
之后)- 上面的
-flto -emit-llvm
(link-time optimization, for whole-program optimization: inlineing functions between source files (or at least seeing if它们是否有副作用,即使没有内联。)gcc 也有-flto
。 gcc -O3 -march=native -fno-stack-protector -fprofile-use
- 以上
-ffast-math
甚至-Ofast
我想 -Os
也值得一试。对齐的怪癖可以提供帮助,即使 I-cache / uop-cache 未命中不是问题。
如果 -fomit-frame-pointer
不是编译器的默认值,也可以使用它来释放额外的整数寄存器并缩短函数 prologue/epilogue.
如果您想在所有机器上使用相同的二进制文件,那么 -march=some_baseline -mtune=something
。 (假设 clang 共享 gcc 的 arch/tune 选项。)
或者只是 -msse4.2 -mtune=sandybridge
之类的。我认为,只要您正在构建 x86-64 二进制文件,编译器就只会对新的 SSE 指令感兴趣。 (不是 popcnt、BMI 等)
另一种方法是在每台机器上的主目录中进行源代码检出,并使用本地编译器构建程序。但是如果你在一台机器上有一个真正新版本的 gcc 或 clang,或英特尔的编译器,那么使用它是有意义的。
您还可以查看自动并行化:gcc -ftree-parallelize-loops=n
,其中 n
是要使用的线程数。
所有这一切的警告 是我听说过使用 -O3
破坏代码,因为它依赖于语言规则不需要的行为。如此激进的优化找到了一种优化方法,使代码不再做同样的事情。如果您希望您的代码 运行 快速,请确保避免未定义的行为,以便您可以将优化器一直调高。 (IIRC,最近有一个关于检查编译器何时基于假设优化某些东西的问题,因为否则会发生未定义的行为。)