我知道"为什么我的编译器要这样做;不是最好的问题,但这一个对我来说真的很奇怪,我完全困惑了。
我曾认为std::min()
与手写的三进制相同(可能有一些编译时模板的东西(,并且在正常使用时,它似乎可以编译成相同的操作。然而,当试图制作";min和sum";循环自动矢量化它们似乎不一样,如果有人能帮我找出原因,我会很高兴。下面是一个产生问题的小示例代码:
#pragma GCC target ("avx2")
#pragma GCC optimize ("O3")
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define N (1<<20)
char a[N], b[N];
int main() {
for (int i=0; i<N; ++i) {
a[i] = rand()%100;
b[i] = rand()%100;
}
int ans = 0;
#pragma GCC ivdep
for (int i=0; i<N; ++i) {
//ans += std::min(a[i], b[i]);
ans += a[i]>b[i] ? a[i] : b[i];
}
printf("%dn", ans);
}
我使用编译命令g++ -o test test.cpp -ftree-vectorize -fopt-info-vec-missed -fopt-info-vec-optimized -funsafe-math-optimizations
在gcc 9.3.0
上编译它。
上面的代码在编译过程中被调试为:
test.cpp:19:17: optimized: loop vectorized using 32 byte vectors
相反,如果我注释三元并取消注释std::min
,我得到的是:
test.cpp:19:17: missed: couldn't vectorize loop
test.cpp:20:35: missed: statement clobbers memory: _9 = std::min<char> (_8, _7);
因此,std::min()
似乎在做一些不同寻常的事情,使gcc无法理解这只是一个min操作。这是标准造成的吗?还是实现失败?或者是否有某种编译标志可以使其工作?
总结:不要使用#pragma GCC optimize
。在命令行中使用-O3
,您将获得所需的行为
GCC关于#pragma GCC optimize
的文件中写道:
在此点之后定义的每个函数都被视为已为每个字符串参数声明了一个
optimize(string)
属性。
并且optimize
属性被记录为:
optimize属性用于指定要使用与命令行上指定的优化选项不同的优化选项编译函数。[…]优化属性只能用于调试目的。它不适用于生产代码[强调,感谢Peter Cordes发现最后一部分。]
所以,不要使用它。
特别是,在文件顶部指定#pragma GCC optimize ("O3")
实际上并不等同于在命令行中使用-O3
。事实证明,前者不会导致std::min
被内联,因此编译器实际上假设它可能会修改全局内存,例如a,b
数组。这自然会抑制矢量化。
仔细阅读__attribute__((optimize))
的文档,会发现函数main()
和std::min()
中的每个都将像使用-O3
一样进行编译。但这与将两者与-O3
一起编译不同,因为只有在后一种情况下,才可以使用内联等过程间优化。
这里有一个关于godbolt的非常简单的例子。使用#pragma GCC optimize ("O3")
,函数foo()
和please_inline_me()
都得到了优化,但please_inline_me()
没有内联。但在命令行上有了-O3
,它确实做到了。
一种猜测是,optimize
属性(扩展名为#pragma GCC optimize
(会导致编译器将函数视为其定义在使用指定选项编译的单独源文件中。事实上,如果std::min()
和main()
是在单独的源文件中定义的,那么您可以使用-O3
编译每一个,但不会得到内联。
可以说,GCC手册应该更明确地记录这一点,尽管我想如果它只是用于调试,那么可以公平地假设它是为熟悉区别的专家准备的。
如果你真的在命令行上用-O3
编译了你的例子,那么你会得到两个版本相同的(矢量化的(汇编,或者至少我做到了。(修复反向比较后:您的三元代码计算的是最大值,而不是最小值。(