std::min 与使用 #pragma GCC 优化(“O3”)的三元 gcc 自动矢量化

Mal*_*sen 9 c++ optimization gcc compiler-optimization auto-vectorization

我知道“为什么我的编译器要这样做”不是最好的问题类型,但这个问题对我来说真的很奇怪,我很困惑。

我原以为这std::min()与手写的三元相同(可能还有一些编译时模板的东西),并且在正常使用时似乎可以编译为相同的操作。但是,当尝试使“最小和总和”循环自动向量化时,它们似乎不一样,如果有人能帮我找出原因,我会很高兴。这是一个产生问题的小示例代码:

#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("%d\n", ans);
}
Run Code Online (Sandbox Code Playgroud)

gcc 9.3.0用编译命令编译它g++ -o test test.cpp -ftree-vectorize -fopt-info-vec-missed -fopt-info-vec-optimized -funsafe-math-optimizations

上面的代码在编译期间调试为:

test.cpp:19:17: optimized: loop vectorized using 32 byte vectors
Run Code Online (Sandbox Code Playgroud)

相反,如果我评论三元并取消评论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);
Run Code Online (Sandbox Code Playgroud)

所以std::min()似乎在做一些不寻常的事情,阻止 gcc 理解它只是一个最小的操作。这是标准造成的吗?还是执行失败?或者是否有一些编译标志可以使这项工作?

Nat*_*dge 9

总结:不要使用#pragma GCC optimize. -O3改为在命令行上使用,您将获得预期的行为。

海湾合作委员会的文件#pragma GCC optimize说:

在这一点之后定义的每个函数都被视为已经optimize(string)为每个字符串参数声明了一个属性。

optimize属性记录为

optimize 属性用于指定要使用与命令行中指定的优化选项不同的优化选项来编译函数。[...]优化属性应仅用于调试目的。它不适用于生产代码。 [强调,感谢彼得科德斯发现最后一部分。]

所以,不要使用它。

特别是,看起来#pragma GCC optimize ("O3")在文件顶部指定实际上并不等同-O3于在命令行上使用。事实证明,前者不会导致std::min被内联,因此编译器实际上确实假设它可能会修改全局内存,例如您的a,b数组。这自然会抑制矢量化。

仔细阅读 for 的文档__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在命令行上编译你的例子,你会得到两个版本相同的(矢量化)程序集,或者至少我做到了。(在修复向后比较之后:您的三元代码正在计算最大值而不是最小值。)

  • GCC的手册[还说](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-optimize-function-attribute) *优化**属性**应该用于调试仅供参考。它不适合生产代码。* - 我认为这也适用于编译指示。 (5认同)