3

両方std::vector<double> X,YのサイズN( ありN%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 にアップグレードできます。

4

2 に答える 2

3

アセンブリを見ましたか?置いた

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/に入り、GCC 4.9.2 のアセンブリを見て-O3 -mfma

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

だからfmaを使う。ただし、ループはベクトル化されません ( sinsdは単一 (つまり、パックされていない) をd意味し、double 浮動小数点を意味します)。

ループをベクトル化するには、連想演算を有効にする必要があります-Ofast。使用-Ofast -mavx2 -mfmaすると

.L8:
        vmovupd (%rax,%rsi), %xmm2
        addq    $1, %r10
        vinsertf128     $0x1, 16(%rax,%rsi), %ymm2, %ymm2
        vfmadd231pd     (%r12,%rsi), %ymm2, %ymm1
        addq    $32, %rsi
        cmpq    %r10, %rdi
        ja      .L8

これでベクトル化されました (pdはパックされた double を意味します)。ただし、展開されていません。これは現在、GCC の制限です。依存チェーンのため、数回展開する必要があります。コンパイラにこれを行わせたい場合は、4 回展開する Clang を使用することを検討してください。それ以外の場合は、組み込み関数を使用して手動で展開します。

GCC とは異なり、Clang はデフォルトで fma を使用しないことに注意してください-mfma。Clang で fma を使用するには-ffp-contract=fast(eg -O3 -mfma -ffp-contract=fast) を使用するか#pragma STDC FP_CONTRACT ON、eg で連想演算-Ofastを有効にします。

異なるコンパイラで fma を有効にする方法の詳細については、融合乗算加算とデフォルトの丸めモードおよび https://stackoverflow.com/a/34461738/2542702 を参照ください。


GCC は、N8 の倍数ではなく、ミスアラインメントを処理するために多くの余分なコードを作成します。コンパイラに、配列が整列されていると仮定し、__builtin_assume_alignedN が 8 の倍数であると仮定することができます。N & -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    $4, %edx
        vxorpd  %xmm0, %xmm0, %xmm0
        shrl    $2, %edx
        xorl    %ecx, %ecx
        leal    1(%rdx), %eax
        xorl    %edx, %edx
.L3:
        vmovapd (%rsi,%rdx), %ymm2
        addl    $1, %ecx
        vfmadd231pd     (%rdi,%rdx), %ymm2, %ymm0
        addq    $32, %rdx
        cmpl    %eax, %ecx
        jb      .L3
        vhaddpd %ymm0, %ymm0, %ymm0
        vperm2f128      $1, %ymm0, %ymm0, %ymm1
        vaddpd  %ymm1, %ymm0, %ymm0
        vzeroupper
        ret
.L4:
        vxorpd  %xmm0, %xmm0, %xmm0
        ret
于 2016-02-17T13:15:33.070 に答える
0

これでそこまでたどり着くかどうかはわかりませんが、解決策の大部分はほぼ確実です。

ループを 2 つに分割する必要があります: 0 から N まで、ステップ M>1 です。M を 16、8、4 にして、asm を確認します。そして、0 から M までの内部ループ。数学反復子の数学について心配する必要はありません。Gcc は十分にスマートです。

Gcc は内側のループをアンロールする必要があり、それらは SIMD 可能であり、FMA を使用する可能性があります。

于 2016-02-16T18:25:17.297 に答える