我需要做什么才能让 GCC 4.9 认识到使用 AVX FMA 的机会?

What do I need to do so GCC 4.9 recognizes the opportunity to use AVX FMA?

我有 std::vector<double> X,Y 大小 NN%16==0),我想计算 sum(X[i]*Y[i])。这是 Fused Multiply and Add (FMA) 的经典用例,它在支持 AVX 的处理器上应该很快。我知道我所有的目标 CPU 都是 Intel、Haswell 或更新的。

如何让 GCC 发出该 AVX 代码? -mfma 是解决方案的一部分,但我需要其他开关吗?

std::vector<double>::operator[] 是否阻碍了这一点?我知道我可以变身

size_t N = X.size();
double sum = 0.0;
for (size_t i = 0; i != N; ++i) sum += X[i] * Y[i];

size_t N = X.size();
double sum = 0.0;
double const* Xp = &X[0];
double const* Yp = &X[0];
for (size_t i = 0; i != N; ++i) sum += Xp[i] * Yp[i];

所以编译器可以发现 &X[0] 在循环中没有改变。但这是否足够甚至是必要的?

当前编译器是 GCC 4.9.2、Debian 8,但如果需要可以升级到 GCC 5。

我不确定这是否会让您一路走来,但我几乎可以肯定这是解决方案的重要组成部分。

您必须将循环分成两部分:0 到 N,步骤 M>1。我会尝试使用 16、8、4 的 M,然后查看 asm。以及 0 到 M 的内部循环。不用担心数学迭代器数学。 Gcc 很聪明。

Gcc 应该展开内部循环,它们可以对它进行 SIMD,也可能使用 FMA。

你看过程序集了吗?我放

double foo(std::vector<double> &X, std::vector<double> &Y) {
    size_t N = X.size();
    double sum = 0.0;
    for (size_t i = 0; i <N; ++i) sum += X[i] * Y[i];
    return sum;
}

进入 http://gcc.godbolt.org/ 并使用 -O3 -mfma 查看 GCC 4.9.2 中的程序集,我看到

.L3:
        vmovsd  (%rcx,%rax,8), %xmm1
        vfmadd231sd     (%rsi,%rax,8), %xmm1, %xmm0
        addq    , %rax
        cmpq    %rdx, %rax
        jne     .L3

所以它使用了fma。但是,它不会对循环进行矢量化(sd 中的 s 表示单(即未打包),而 d 表示双浮点数)。

要对循环进行矢量化,您需要启用关联数学,例如-Ofast。使用 -Ofast -mavx2 -mfma 得到

.L8:
        vmovupd (%rax,%rsi), %xmm2
        addq    , %r10
        vinsertf128     [=12=]x1, 16(%rax,%rsi), %ymm2, %ymm2
        vfmadd231pd     (%r12,%rsi), %ymm2, %ymm1
        addq    , %rsi
        cmpq    %r10, %rdi
        ja      .L8

所以现在它是矢量化的(pd 表示压缩双打)。但是,它没有展开。这是目前 GCC 的限制。由于依赖链,您需要展开多次。如果您想让编译器为您执行此操作,请考虑使用展开四次的 Clang,否则使用内部函数手动展开。

请注意,与 GCC 不同,Clang 在 -mfma 中默认不使用 fma。为了将 fma 与 Clang 一起使用,请使用 -ffp-contract=fast(例如 -O3 -mfma -ffp-contract=fast)或 #pragma STDC FP_CONTRACT ON 或启用关联数学,例如-Ofast 如果您想使用 Clang 对循环进行矢量化,无论如何您都需要启用关联数学。

有关使用不同编译器启用 fma 的详细信息,请参阅 and 。


GCC 创建了很多额外的代码来处理错位,并且 N 不是 8 的倍数。您可以告诉编译器假设数组使用 __builtin_assume_aligned 对齐并且 N 是一个使用 N & -8

的 8 的倍数

下面的代码用-Ofast -mavx2 -mfma

double foo2(double * __restrict X, double * __restrict Y, int N) {
    X = (double*)__builtin_assume_aligned(X,32);
    Y = (double*)__builtin_assume_aligned(Y,32);
    double sum = 0.0;
    for (int i = 0; i < (N &-8); ++i) sum += X[i] * Y[i];
    return sum;
}

生成以下简单程序集

        andl    $-8, %edx
        jle     .L4
        subl    , %edx
        vxorpd  %xmm0, %xmm0, %xmm0
        shrl    , %edx
        xorl    %ecx, %ecx
        leal    1(%rdx), %eax
        xorl    %edx, %edx
.L3:
        vmovapd (%rsi,%rdx), %ymm2
        addl    , %ecx
        vfmadd231pd     (%rdi,%rdx), %ymm2, %ymm0
        addq    , %rdx
        cmpl    %eax, %ecx
        jb      .L3
        vhaddpd %ymm0, %ymm0, %ymm0
        vperm2f128      , %ymm0, %ymm0, %ymm1
        vaddpd  %ymm1, %ymm0, %ymm0
        vzeroupper
        ret
.L4:
        vxorpd  %xmm0, %xmm0, %xmm0
        ret