以下是我尝试使用SSE加速的示例C代码,两个数组是3072元素长的双精度数,如果我不需要双精度,可以将其下移到浮点数。
double sum = 0.0;
for(k = 0; k < 3072; k++) {
sum += fabs(sima[k] - simb[k]);
}
double fp = (1.0 - (sum / (255.0 * 1024.0 * 3.0)));
无论如何,我目前的问题是如何在SSE寄存器中执行fabs步骤为double或float,以便我可以将整个计算保留在SSE寄存器中,以便它保持快速并且我可以通过部分展开来并行化所有步骤这个循环。
以下是我找到的fabs() asm或可能是flipping the sign - SO的一些资源,但是第二个的弱点需要进行条件检查。
答案 0 :(得分:37)
我建议使用按位和掩码。正值和负值具有相同的表示,只有最高有效位不同,正值为0,负值为1,请参阅double precision number format。您可以使用以下方法之一:
inline __m128 abs_ps(__m128 x) {
static const __m128 sign_mask = _mm_set1_ps(-0.f); // -0.f = 1 << 31
return _mm_andnot_ps(sign_mask, x);
}
inline __m128d abs_pd(__m128d x) {
static const __m128d sign_mask = _mm_set1_pd(-0.); // -0. = 1 << 63
return _mm_andnot_pd(sign_mask, x); // !sign_mask & x
}
此外,展开循环以打破循环携带的依赖关系链可能是个好主意。由于这是非负值的总和,因此求和的顺序并不重要:
double norm(const double* sima, const double* simb) {
__m128d* sima_pd = (__m128d*) sima;
__m128d* simb_pd = (__m128d*) simb;
__m128d sum1 = _mm_setzero_pd();
__m128d sum2 = _mm_setzero_pd();
for(int k = 0; k < 3072/2; k+=2) {
sum1 += abs_pd(_mm_sub_pd(sima_pd[k], simb_pd[k]));
sum2 += abs_pd(_mm_sub_pd(sima_pd[k+1], simb_pd[k+1]));
}
__m128d sum = _mm_add_pd(sum1, sum2);
__m128d hsum = _mm_hadd_pd(sum, sum);
return *(double*)&hsum;
}
通过展开并打破依赖关系(sum1和sum2现在是独立的),您让处理器执行我们的顺序添加。由于指令是在现代CPU上流水线化的,因此CPU可以在前一个完成之前开始处理新的添加。此外,按位操作在单独的执行单元上执行,CPU实际上可以在与加/减相同的周期中执行它。我建议Agner Fog's optimization manuals。
最后,我不建议使用openMP。循环太小,在多个线程中分配作业的开销可能比任何潜在的好处都大。
答案 1 :(得分:9)
-x和x的最大值应为abs(x)。这是代码:
x = _mm_max_ps(_mm_sub_ps(_mm_setzero_ps(), x), x)
答案 2 :(得分:2)
可能最简单的方法如下:
__m128d vsum = _mm_set1_pd(0.0); // init partial sums
for (k = 0; k < 3072; k += 2)
{
__m128d va = _mm_load_pd(&sima[k]); // load 2 doubles from sima, simb
__m128d vb = _mm_load_pd(&simb[k]);
__m128d vdiff = _mm_sub_pd(va, vb); // calc diff = sima - simb
__m128d vnegdiff = mm_sub_pd(_mm_set1_pd(0.0), vdiff); // calc neg diff = 0.0 - diff
__m128d vabsdiff = _mm_max_pd(vdiff, vnegdiff); // calc abs diff = max(diff, - diff)
vsum = _mm_add_pd(vsum, vabsdiff); // accumulate two partial sums
}
请注意,这可能不会比现代x86 CPU上的标量代码更快,后者通常有两个FPU。但是,如果您可以降低到单精度,那么您的吞吐量可能会提高2倍。
另请注意,在循环之后,您需要将vsum
中的两个部分和组合成标量值,但这非常简单,并且不是性能关键。