我是 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 个周期最多进行一次循环瓶颈。 (有关优化资源,请参阅 x86 标签 wiki。)
顺便说一句,向量化的fabsf()
很简单,参见Fastest way to compute absolute value using SSE .那和 SSE 比较小于应该可以为您提供与标量代码相同的语义。 (但是让 -O0
不烂更难)。
手动展开标量版本一两次可能会更好,因为 -O0
太烂了。
关于c - 没有编译器优化的 SSE 内在函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37223499/