SSE 内在函数优化
Posted
技术标签:
【中文标题】SSE 内在函数优化【英文标题】:SSE intrinsics optimisation 【发布时间】:2016-05-14 06:37:35 【问题描述】:我是 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;
【问题讨论】:
为什么要关心性能,向量代码甚至还没有正确。 你能告诉我错误是什么吗?我会改正的。 你不比较fabs(x) <= FLT_EPSILON
,你比较x != 0.0f
,完全不等同于非矢量化版本。
比较x != 0.0f
还是x == 0.0f
不是重点。两者都不等同于fabs(x) <= FLT_EPSILON
。不,SSE 不会神奇地使浮点数学变得无限精确。我会说你的测试很糟糕,不要练习FLT_EPSILON
相关的情况。当然,无论如何,该测试的价值值得怀疑,但我不是就该主题进行演讲的合适人选。
好吧,我还建议您研究一下 FLT_EPSILON
的实际 含义,以及它与浮点不准确性的关系。提示:一般不适合作为两个值之差的绝对误差。
【参考方案1】:
如果您需要使用-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。
标量版本的结果要好得多。它的源代码在一个表达式中完成所有操作,因此临时变量保留在寄存器中。但是,循环计数器仍在内存中,例如,在 Haswell 上,它的瓶颈最多是每 6 个周期迭代一次。 (有关优化资源,请参阅x86 标签 wiki。)
顺便说一句,矢量化fabsf()
很简单,请参阅Fastest way to compute absolute value using SSE。那和 SSE 比较小于应该可以为您提供与标量代码相同的语义。 (但更难让-O0
不烂)。
您最好手动展开标量版本一到两次,因为-O0
太糟糕了。
【讨论】:
【参考方案2】:一些编译器非常擅长对向量进行优化。您是否检查了两个版本的优化构建的生成程序集? “天真的”版本不是实际上使用了 SIMD 或其他优化技术吗?
【讨论】:
是的...但是由于我们的作业要求,编译器中的所有优化标志都已关闭。 @JenniferQ: /facepalm。__m128
变量在-O0
编译器输出中的语句之间被存储/重新加载。为-O0
优化代码与使用-O2
或-O3
实际优化代码以编译为良好的asm 有显着差异。我打算亲自查看 asm on godbolt,但您问题中的代码甚至无法编译。例如变量名称冲突,请参阅该链接中的错误消息。
这是我的错。我从整个程序中截取了代码,但忘记更改某些内容......现在编译应该没问题。以上是关于SSE 内在函数优化的主要内容,如果未能解决你的问题,请参考以下文章