C语言 未进行编译器优化的SSE内部函数

rggaifut  于 2023-01-01  发布在  其他
关注(0)|答案(2)|浏览(173)

我是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;
}

SSE版本:

#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;
    }
eeq64g8w

eeq64g8w1#

如果你需要用-O0编译,那么尽可能在一条语句中完成,在普通代码中,int a=foo(); bar(a);将编译成与bar(foo())相同的asm,但是在-O0代码中,第二个版本可能会更快,因为它不会将结果存储到内存中,然后为下一条语句重新加载它。
-O0被设计成给予最可预测的调试结果,这就是为什么在每条语句之后所有的东西都被存储到内存中,这显然对性能来说是可怕的。
我写a big answer a while ago是为了一个不同的问题,其他人有一个像你这样愚蠢的任务,要求他们为-O0优化。
不要在这个任务上花太多精力,也许你想出的大多数让你的代码在-O0上运行得更快的“技巧”只对-O0有影响,但对启用优化的代码没有影响。
在真实的生活中,通常至少使用clang或gcc -O2编译代码,有时使用-O3 -march=haswell或其他自动矢量化工具(一旦调试完毕,您就可以进行优化)。
回复:您的更新:
现在它编译了,可以看到SSE版本中可怕的asm。我把它和一个实际上也能编译的标量代码沿着放在godbolt上。在禁用优化的情况下,内部函数通常编译得很糟糕,即使使用__attribute__((always_inline)),内联函数仍然有args和返回值,导致actual load/store round trips(存储转发延迟)。例如,请参见 Demonstrator code failing to show 4 times faster SIMD speed with optimization disabled
标量版本的结果要好得多。它的源代码在一个表达式中完成所有操作,因此临时变量都留在寄存器中。不过,循环计数器仍然在内存中,例如,在Haswell上,这将其瓶颈限制为每6个循环最多迭代一次。(有关优化资源,请参见x86标签wiki)
顺便说一句,向量化fabsf()很容易,请参阅使用SSE计算绝对值的最快方法。它和SSE比较小于应该会给你提供与标量代码相同的语义。(但使-O0更难不吸)。
最好手动展开标量版本一两次,因为-O0太糟糕了。

ykejflvf

ykejflvf2#

有些编译器在向量优化方面做得很好。你检查过两个版本的优化编译生成的汇编了吗?“幼稚”版本实际上不是使用SIMD或其他优化技术吗?

相关问题