对SSE指令进行基准测试

hor*_*guy 2 benchmarking assembly gcc sse

我正在对一些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;
}
Run Code Online (Sandbox Code Playgroud)

yps*_*psu 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)
Run Code Online (Sandbox Code Playgroud)

最内层循环体的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)
Run Code Online (Sandbox Code Playgroud)

我不确定这个,但这是我的猜测:

如您所见,编译器只将4个浮动值存储为4个32位存储.然后通过16字节加载读回.这会导致存储转发停顿,这在发生时很昂贵.您可以在英特尔手册中查找.它不会出现在标量版本中,这会使性能产生差异.

为了加快速度,您需要确保不会发生这种失速.如果您使用4个浮点数的常量数组,请将其设为const并将结果存储在另一个对齐的数组中.这样编译器希望在加载之前不会产生那些不必要的4字节mov.或者,如果需要填充结果数组,请使用16字节存储命令.如果您无法避免这些4字节的mov,则需要在存储之后但在加载之前执行其他操作(例如计算其他内容).

  • 我已更新帖子以包含答案. (2认同)