2

同じことを行う従来の C コードに対して、いくつかのSSEコード (4 つの float を 4 つの float で乗算する) のベンチマークを行っています。非 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;
}
4

1 に答える 1

5

最適化を有効にすると、非 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 バイトのロードによって読み戻されます。これによりストア フォワーディング ストールが発生し、発生するとコストがかかります。これは Intel のマニュアルで調べることができます。これはスカラー バージョンでは発生せず、これがパフォーマンスの違いになります。

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

于 2009-11-14T15:39:26.677 に答える