没有编译器优化的 SSE 内在函数

Jen*_*r Q 0 c sse simd compiler-optimization

我是 SSE 内在函数的新手,并尝试通过它来优化我的代码。这是我的程序,用于计算等于给定值的数组元素。

我将代码更改为 SSE 版本,但速度几乎没有改变。我想知道我是否以错误的方式使用SSE......

此代码用于不允许我们启用编译器优化选项的分配。

无 SSE 版本:

int get_freq(const float* matrix, float value) {

    int freq = 0;

    for (ssize_t i = start; i < end; i++) {
        if (fabsf(matrix[i] - value) <= FLT_EPSILON) {
            freq++;
        }
    }

    return freq;
}
Run Code Online (Sandbox Code Playgroud)

上交所版本:

#include <immintrin.h>
#include <math.h>
#include <float.h>

#define GETLOAD(n) __m128 load##n = _mm_load_ps(&matrix[i + 4 * n])
#define GETEQU(n) __m128 check##n = _mm_and_ps(_mm_cmpeq_ps(load##n, value), and_value)
#define GETCOUNT(n) count = _mm_add_ps(count, check##n)

    int get_freq(const float* matrix, float givenValue, ssize_t g_elements) {

        int freq = 0;
        int i;

        __m128 value = _mm_set1_ps(givenValue);
        __m128 count = _mm_setzero_ps();
        __m128 and_value = _mm_set1_ps(0x00000001);


        for (i = 0; i + 15 < g_elements; i += 16) {
            GETLOAD(0); GETLOAD(1); GETLOAD(2); GETLOAD(3);
            GETEQU(0);  GETEQU(1);  GETEQU(2);  GETEQU(3);
            GETCOUNT(0);GETCOUNT(1);GETCOUNT(2);GETCOUNT(3);
        }

        __m128 shuffle_a = _mm_shuffle_ps(count, count, _MM_SHUFFLE(1, 0, 3, 2));
        count = _mm_add_ps(count, shuffle_a);
        __m128 shuffle_b = _mm_shuffle_ps(count, count, _MM_SHUFFLE(2, 3, 0, 1));
        count = _mm_add_ps(count, shuffle_b);
        freq = _mm_cvtss_si32(count);


        for (; i < g_elements; i++) {
            if (fabsf(matrix[i] - givenValue) <= FLT_EPSILON) {
                freq++;
            }
        }

        return freq;
    }
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 5

如果您需要使用 进行编译-O0,请在一条语句中尽可能多地执行操作。在正常代码中,int a=foo(); bar(a);将编译为与 相同的 asm bar(foo()),但在-O0代码中,第二个版本可能会更快,因为它不会将结果存储到内存中,然后为下一条语句重新加载它。

-O0旨在通过调试提供最可预测的结果,这就是为什么在每个语句之后所有内容都存储到内存中的原因。这对于性能来说显然是可怕的。

不久前,我针对一个不同的问题写了一个很大的答案,这个问题来自其他人,他们有一个像你这样的愚蠢任务,需要他们优化-O0。其中一些可能会有所帮助。


不要在这项任务上太努力。可能您发现的使代码运行得更快的大多数“技巧”-O0只对 有用-O0,但对于启用优化没有任何影响。

在现实生活中,代码通常-O2至少使用 clang 或 gcc 进行编译,有时-O3 -march=haswell或其他方式进行自动矢量化。(调试完成后,您就可以进行优化了。)


回复:您的更新:

现在它可以编译了,可以看到 SSE 版本中可怕的 asm。我也将它与实际编译的标量代码版本一起放在 godbolt 上。内在函数通常在禁用优化的情况下编译得非常糟糕,内联函数仍然具有参数和返回值,导致实际的加载/存储往返(存储转发延迟),即使使用__attribute__((always_inline)). 例如,请参阅演示器代码在禁用优化的情况下无法显示 4 倍快的 SIMD 速度。

标量版本的结果要好得多。它的源在一个表达式中完成所有操作,因此临时值保留在寄存器中。不过,循环计数器仍然在内存中,例如,在 Haswell 上,它的瓶颈最多为每 6 个周期一次迭代。(有关优化资源,请参阅标签 wiki。)


顺便说一句,矢量化fabsf()很容易,请参阅使用 SSE 计算绝对值的最快方法。与小于的 SSE 比较应该可以为您提供与标量代码相同的语义。(但更难做到-O0不烂)。

您可能会做得更好,只需手动展开标量版本一两次,因为-O0太糟糕了。