首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用SSE4实现点积计算的矢量化

用SSE4实现点积计算的矢量化
EN

Stack Overflow用户
提问于 2012-07-04 09:04:01
回答 3查看 3.1K关注 0票数 10

我正在尝试使用SSE4点产品来改进这段代码,但我很难找到解决方案。此函数获取参数qi和tj,这两个参数包含每个80个单元的浮点数组,然后计算点积。返回值是一个具有四个点积的向量。所以我想要做的是,并行计算四个点乘,共二十个值。

你知道如何改进这段代码吗?

代码语言:javascript
复制
inline __m128 ScalarProd20Vec(__m128* qi, __m128* tj)
{
    __m128 res=_mm_add_ps(_mm_mul_ps(tj[0],qi[0]),_mm_mul_ps(tj[1],qi[1]));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[2],qi[2]),_mm_mul_ps(tj[3],qi[3])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[4],qi[4]),_mm_mul_ps(tj[5],qi[5])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[6],qi[6]),_mm_mul_ps(tj[7],qi[7])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[8],qi[8]),_mm_mul_ps(tj[9],qi[9])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[10],qi[10]),_mm_mul_ps(tj[11],qi[11])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[12],qi[12]),_mm_mul_ps(tj[13],qi[13])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[14],qi[14]),_mm_mul_ps(tj[15],qi[15])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[16],qi[16]),_mm_mul_ps(tj[17],qi[17])));
    res=_mm_add_ps(res,_mm_add_ps(_mm_mul_ps(tj[18],qi[18]),_mm_mul_ps(tj[19],qi[19])));
    return res;
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-07-04 10:36:32

在我在SO上看到的数百个SSE示例中,您的代码是为数不多的从一开始就状态良好的示例之一。您不需要SSE4点积说明。(您可以做得更好!)

然而,有一件事你可以尝试:(我说尝试是因为我还没有计时)。

目前,您在res上有一个数据依赖链。在今天的大多数机器上,向量加法需要3-4个周期。所以你的代码至少需要30个周期才能运行,因为你有:

代码语言:javascript
复制
(10 additions on critical path) * (3 cycles addps latency) = 30 cycles

您可以做的是对res变量进行节点拆分,如下所示:

代码语言:javascript
复制
__m128 res0 = _mm_add_ps(_mm_mul_ps(tj[ 0],qi[ 0]),_mm_mul_ps(tj[ 1],qi[ 1]));
__m128 res1 = _mm_add_ps(_mm_mul_ps(tj[ 2],qi[ 2]),_mm_mul_ps(tj[ 3],qi[ 3]));

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[ 4],qi[ 4]),_mm_mul_ps(tj[ 5],qi[ 5]))); 
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[ 6],qi[ 6]),_mm_mul_ps(tj[ 7],qi[ 7])));

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[ 8],qi[ 8]),_mm_mul_ps(tj[ 9],qi[ 9])));
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[10],qi[10]),_mm_mul_ps(tj[11],qi[11])));

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[12],qi[12]),_mm_mul_ps(tj[13],qi[13])));
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[14],qi[14]),_mm_mul_ps(tj[15],qi[15])));

res0 = _mm_add_ps(res0,_mm_add_ps(_mm_mul_ps(tj[16],qi[16]),_mm_mul_ps(tj[17],qi[17])));
res1 = _mm_add_ps(res1,_mm_add_ps(_mm_mul_ps(tj[18],qi[18]),_mm_mul_ps(tj[19],qi[19])));

return _mm_add_ps(res0,res1);

这几乎将你的关键路径减半。请注意,由于浮点非关联性,编译器进行此优化是非法的。

这是一个使用4路节点拆分和AMD FMA4指令的替代版本。如果您不能使用融合乘法加法,请随意拆分它们。它可能仍然比上面的第一个版本更好。

代码语言:javascript
复制
__m128 res0 = _mm_mul_ps(tj[ 0],qi[ 0]);
__m128 res1 = _mm_mul_ps(tj[ 1],qi[ 1]);
__m128 res2 = _mm_mul_ps(tj[ 2],qi[ 2]);
__m128 res3 = _mm_mul_ps(tj[ 3],qi[ 3]);

res0 = _mm_macc_ps(tj[ 4],qi[ 4],res0);
res1 = _mm_macc_ps(tj[ 5],qi[ 5],res1);
res2 = _mm_macc_ps(tj[ 6],qi[ 6],res2);
res3 = _mm_macc_ps(tj[ 7],qi[ 7],res3);

res0 = _mm_macc_ps(tj[ 8],qi[ 8],res0);
res1 = _mm_macc_ps(tj[ 9],qi[ 9],res1);
res2 = _mm_macc_ps(tj[10],qi[10],res2);
res3 = _mm_macc_ps(tj[11],qi[11],res3);

res0 = _mm_macc_ps(tj[12],qi[12],res0);
res1 = _mm_macc_ps(tj[13],qi[13],res1);
res2 = _mm_macc_ps(tj[14],qi[14],res2);
res3 = _mm_macc_ps(tj[15],qi[15],res3);

res0 = _mm_macc_ps(tj[16],qi[16],res0);
res1 = _mm_macc_ps(tj[17],qi[17],res1);
res2 = _mm_macc_ps(tj[18],qi[18],res2);
res3 = _mm_macc_ps(tj[19],qi[19],res3);

res0 = _mm_add_ps(res0,res1);
res2 = _mm_add_ps(res2,res3);

return _mm_add_ps(res0,res2);
票数 9
EN

Stack Overflow用户

发布于 2012-07-04 09:48:25

首先,你能做的最重要的优化是确保你的编译器打开了所有的优化设置。

编译器非常聪明,所以如果把它写成一个循环,它很可能会展开它:

代码语言:javascript
复制
__128 res = _mm_setzero();
for (int i = 0; i < 10; i++) {
  res = _mm_add_ps(res, _mm_add_ps(_mm_mul_ps(tj[2*i], qi[2*i]), _mm_mul_ps(tj[2*i+1], qi[2*i+1])));
}
return res;

(对于GCC,你需要传递-funroll-loops,然后它会展开它来一次进行5次迭代。)

如果循环版本较慢,您还可以定义一个宏并手动展开它,例如:

代码语言:javascript
复制
__128 res = _mm_setzero();

#define STEP(i) res = _mm_add_ps(res, _mm_add_ps(_mm_mul_ps(tj[2*i], qi[2*i]), _mm_mul_ps(tj[2*i+1], qi[2*i+1])))

STEP(0); STEP(1); STEP(2); STEP(3); STEP(4);
STEP(5); STEP(6); STEP(7); STEP(8); STEP(9);

#undef STEP

return res;

您甚至可以运行从0到20的循环(或对宏版本执行相同的操作),即:

代码语言:javascript
复制
__128 res = _mm_setzero();
for (int i = 0; i < 20; i++) {
  res = _mm_add_ps(res, _mm_mul_ps(tj[i], qi[i]));
}
return res;

(对于GCC和-funroll-loops,这将展开为一次10次迭代,即与上面的一次两次循环相同。)

票数 3
EN

Stack Overflow用户

发布于 2012-07-04 09:52:41

您的数据没有按照专门的SSE4点积指令(dpps)的适当格式排列在内存中。这些指令期望单个向量的维度是相邻的,如下所示:

代码语言:javascript
复制
| dim0 | dim1 | dim2 | ... | dim19 |

然而,您的数据似乎具有相互交错的向量:

代码语言:javascript
复制
| v0-dim0 | v1-dim0 | v2-dim0 | v3-dim0 | v0-dim1 | ...

您目前的通用方法似乎是合适的-您可以通过重新排序指令来改进事情,以便乘法的结果在生成后不会立即使用,但实际上编译器应该能够自己解决这一问题。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11321205

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档