質問

いくつかのベンチマークを行っています SSE コード (4 つの浮動小数点数と 4 つの浮動小数点の乗算) を、同じことを行う従来の C コードと比較します。非 SSE コードは SSE よりも 2 ~ 3 倍高速であると思われるため、私のベンチマーク コードは何らかの点で間違っているに違いないと思います。

以下のベンチマーク コードのどこが間違っているのか誰か教えていただけますか?そしておそらく、SSE コードと非 SSE コードの両方の速度を正確に示す別のアプローチを提案してください。

#include <time.h>
#include <string.h>
#include <stdio.h>

#define ITERATIONS 100000

#define MULT_FLOAT4(X, Y) ({ \
asm volatile ( \
    "movaps (%0), %%xmm0\n\t" \
    "mulps (%1), %%xmm0\n\t" \
    "movaps %%xmm0, (%1)" \
    :: "r" (X), "r" (Y)); })

int main(void)
{
    int i, j;
    float a[4] __attribute__((aligned(16))) = { 10, 20, 30, 40 };
    time_t timer, sse_time, std_time;

    timer = time(NULL);
    for(j = 0; j < 5000; ++j)
        for(i = 0; i < ITERATIONS; ++i) {
            float b[4] __attribute__((aligned(16))) = { 0.1, 0.1, 0.1, 0.1 };

            MULT_FLOAT4(a, b);

        }
    sse_time = time(NULL) - timer;

    timer = time(NULL);
    for(j = 0; j < 5000; ++j)
        for(i = 0; i < ITERATIONS; ++i) {
            float b[4] __attribute__((aligned(16))) = { 0.1, 0.1, 0.1, 0.1 };

            b[0] *= a[0];
            b[1] *= a[1];
            b[2] *= a[2];
            b[3] *= a[3];

    }
    std_time = time(NULL) - timer;

    printf("sse_time %d\nstd_time %d\n", sse_time, std_time);

    return 0;
}
役に立ちましたか?

解決

最適化を有効にすると、非 SSE コードは完全に削除されますが、SSE コードはそのまま残るため、このケースは簡単です。さらに興味深いのは、最適化がオフになっている場合です。この場合、SSE コードは依然として遅くなりますが、ループのコードは同じです。

最も内側のループ本体の非 SSE コード:

movl    $0x3dcccccd, %eax
movl    %eax, -80(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -76(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -72(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -68(%rbp)
movss   -80(%rbp), %xmm1
movss   -48(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -80(%rbp)
movss   -76(%rbp), %xmm1
movss   -44(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -76(%rbp)
movss   -72(%rbp), %xmm1
movss   -40(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -72(%rbp)
movss   -68(%rbp), %xmm1
movss   -36(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -68(%rbp)

最も内側のループ本体の SSE コード:

movl    $0x3dcccccd, %eax
movl    %eax, -64(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -60(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -56(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -52(%rbp)
leaq    -48(%rbp), %rax
leaq    -64(%rbp), %rdx
movaps (%rax), %xmm0
mulps (%rdx), %xmm0
movaps %xmm0, (%rdx)

これについてはわかりませんが、私の推測は次のとおりです。

ご覧のとおり、コンパイラは 4 つの浮動小数点値を 4 つの 32 ビット ストアに格納するだけです。次に、これは 16 バイトのロードによって読み戻されます。これにより、ストア転送の停止が発生し、コストが高くなります。これはインテルのマニュアルで調べることができます。スカラー バージョンではこの問題は発生しないため、パフォーマンスに違いが生じます。

高速化するには、このストールが発生しないようにする必要があります。4 つの浮動小数点の定数配列を使用している場合は、それを const にして、結果を別の整列された配列に格納します。こうすることで、コンパイラはロード前に不要な 4 バイト mov を作成しなくなります。または、結果の配列を埋める必要がある場合は、16 バイトのストア コマンドを使用して実行します。これらの 4 バイト mov を回避できない場合は、ストアの後、ロードの前に別のことを行う必要があります (たとえば、何か他のことを計算するなど)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top