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

标签 c compiler-optimization simd sse

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

上海证券交易所版本:

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

最佳答案

如果您需要使用-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。我把它on godbolt along with a version of the scalar code that actually compiles, too .内部函数通常在禁用优化的情况下编译得非常糟糕,内联函数仍然具有导致 actual load/store round trips 的 args 和返回值。 (存储转发延迟)即使使用 __attribute__((always_inline))。参见 Demonstrator code failing to show 4 times faster SIMD speed with optimization disabled 例如。

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


顺便说一句,向量化的fabsf() 很简单,参见Fastest way to compute absolute value using SSE .那和 SSE 比较小于应该可以为您提供与标量代码相同的语义。 (但是让 -O0 不烂更难)。

手动展开标量版本一两次可能会更好,因为 -O0 太烂了。

关于c - 没有编译器优化的 SSE 内在函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37223499/

相关文章:

统计最大数出现的次数

c - 按字母顺序在c中按字母顺序排列字符串

c# -/optimize 标志启用了哪些运行时优化?

cpu - 编译器完成的指令重新排序与 cpu 完成的指令重新排序之间有什么关系?

bit-manipulation - 计算 AVX2 向量中每个元素的前导零位,模拟 _mm256_lzcnt_epi32

c - 将 __m256i 存储为整数

无效读/写会导致 SIGBUS 错误吗?

gcc - 对于基于 Sandy Bridge 的 Pentium,正确的特定于体系结构的选项 (-m) 是什么?

c++ - 具有可扩展 vector 扩展 (SVE) 的 ARM V-8

c++ - MPI,更大的阵列