SSE 命令のベンチマーク
-
20-09-2019 - |
質問
いくつかのベンチマークを行っています 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 を回避できない場合は、ストアの後、ロードの前に別のことを行う必要があります (たとえば、何か他のことを計算するなど)。