我有一个通用代码,我正试图移动到SSE,以加快它的速度,因为它经常被调用。所讨论的代码基本上如下所示:
for (int i = 1; i < mysize; ++i)
{
buf[i] = myMin(buf[i], buf[i - 1] + offset);
}myMin在哪里是简单的min函数(a < b)?B:(我看过拆卸,这里有跳转)
我的SSE代码(我已经经历了几次迭代以加快)现在是在这个表单上:
float tmpf = *(tmp - 1);
__m128 off = _mm_set_ss(offset);
for (int l = 0; l < mysize; l += 4)
{
__m128 post = _mm_load_ps(tmp);
__m128 pre = _mm_move_ss(post, _mm_set_ss(tmpf));
pre = _mm_shuffle_ps(pre, pre, _MM_SHUFFLE(0, 3, 2, 1));
pre = _mm_add_ss(pre, off);
post = _mm_min_ss(post, pre);
// reversed
pre = _mm_shuffle_ps(post, post, _MM_SHUFFLE(2, 1, 0, 3));
post = _mm_add_ss(post, off );
pre = _mm_min_ss(pre, post);
post = _mm_shuffle_ps(pre, pre, _MM_SHUFFLE(2, 1, 0, 3));
pre = _mm_add_ss(pre, off);
post = _mm_min_ss(post, pre);
// reversed
pre = _mm_shuffle_ps(post, post, _MM_SHUFFLE(2, 1, 0, 3));
post = _mm_add_ss(post, off);
pre = _mm_min_ss(pre, post);
post = _mm_shuffle_ps(pre, pre, _MM_SHUFFLE(2, 1, 0, 3));
_mm_store_ps(tmp, post);
tmpf = tmp[3];
tmp += 4;
}忽略任何边缘案例场景(我处理得很好),而且由于buf/tmp的大小,这些方案的开销可以忽略不计,有人能解释为什么SSE版本比2x慢吗?VTune一直将其归因于L1的失误,但正如我所看到的,它应该减少4倍到L1的旅行,并且没有分支/跳转,所以应该更快一些,但它不是。我搞错什么了?
谢谢
编辑:所以我确实在一个单独的测试用例中找到了其他的东西。我原以为这不会有什么关系,但遗憾的是,事实确实如此。所以我上面的尺寸其实没那么大(大约30-50),但是有很多这样的东西,它们都是连续的。在这种情况下,三元表达式比SSE快。但是,如果以百万为单位,并且只有30-50次迭代,则SSE版本会更快。知道为什么吗?我认为记忆交互对两者都是一样的,包括先发制人的先发制人.
发布于 2014-09-16 11:25:53
如果此代码对性能至关重要,则必须查看所获得的数据。这是连环依赖正在杀死你,你需要摆脱它。
一个非常小的值-- buf I--将影响以下许多值。例如,如果偏移量= 1,buf = 0,以及所有其他值都大于100万,那么一个值将影响下一个100万。另一方面,这种事情可能很少发生。
如果很少见,他们会检查buf I> buf I+偏移量是否完全矢量化,如果是,则替换它,并跟踪所做的更改,而不考虑buf I值可能向上滚动。然后检查更改的位置,然后重新检查它们。
在极端情况下,假设buf i总是在0到1之间,偏移量> 0.5,您知道buf i根本无法影响buf i+2,所以您只需忽略串行依赖,并并行地、完全向量化地执行所有操作。
另一方面,如果缓冲区中有一些影响大量连续值的微小值,则从第一个值buf开始,并完全向量化地检查buf i< buf +I*偏移量,替换值,直到检查失败。
你说“价值可以是任何东西”。如果是这样的话,例如,如果buf i被随机选择在0到1,000,000之间,并且偏移量不是很大,那么就会有元素buf i,这会迫使大量的元素成为buf i+ (k - i) *偏移。例如,如果偏移量= 1,并且您发现buf i约为10,000,那么它将强制平均大约100个值等于buf i+ (k - i) *偏移量。
发布于 2014-09-16 09:19:18
这里有一个没有分支的解决方案,你可以试试
for (int i = 1; i < mysize; i++) {
float a = buf[i];
float b = buf[i-1] + offset;
buf[i] = b + (a<b)*(a-b);
}这是程序集:
.L6:
addss xmm0, xmm4
movss xmm1, DWORD PTR [rax]
movaps xmm2, xmm1
add rax, 4
movaps xmm3, xmm6
cmpltss xmm2, xmm0
subss xmm1, xmm0
andps xmm3, xmm2
andnps xmm2, xmm5
orps xmm2, xmm3
mulss xmm1, xmm2
addss xmm0, xmm1
movss DWORD PTR [rax-4], xmm0
cmp rax, rdx
jne .L6但是带有分支的版本可能已经更好了。
for (int i = 1; i < mysize; i++) {
float a = buf[i];
float b = buf[i-1] + offset;
buf[i] = a<b ? a : b;
}这是程序集
.L15:
addss xmm0, xmm2
movss xmm1, DWORD PTR [rax]
add rax, 4
minss xmm1, xmm0
movss DWORD PTR [rax-4], xmm1
cmp rax, rdx
movaps xmm0, xmm1
jne .L15这会产生使用minss (cmp rax, rdx应用于循环迭代器)的代码。
最后,下面是您可以与MSVC一起使用的代码,它生成与GCC相同的程序集,后者是无分支的。
__m128 offset4 = _mm_set1_ps(offset);
for (int i = 1; i < mysize; i++) {
__m128 a = _mm_load_ss(&buf[i]);
__m128 b = _mm_load_ss(&buf[i-1]);
b = _mm_add_ss(b, offset4);
a = _mm_min_ss(a,b);
_mm_store_ss(&buf[i], a);
}下面是另一种使用分支的表单
__m128 offset4 = _mm_set1_ps(offset);
for (int i = 1; i < mysize; i++) {
__m128 a = _mm_load_ss(&buf[i]);
__m128 b = _mm_load_ss(&buf[i-1]);
b = _mm_add_ss(b, offset4);
if(_mm_comige_ss(b,a))
_mm_store_ss(&buf[i], b);
}https://stackoverflow.com/questions/25858858
复制相似问题