【发布时间】:2014-09-16 00:17:50
【问题描述】:
我有一个通用代码,我正试图将其移至 SSE 以加快它的速度,因为它经常被调用。有问题的代码基本上是这样的:
for (int i = 1; i < mysize; ++i)
{
buf[i] = myMin(buf[i], buf[i - 1] + offset);
}
myMin 是你的简单 min 函数 (a
我的 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 版本慢 2 倍? VTune 一直将其归因于 L1 未命中,但正如我所见,它应该减少 4 倍的 L1 行程并且没有分支/跳转,所以它应该更快,但事实并非如此。我在这里误会了什么?
谢谢
编辑: 所以我确实在一个单独的测试用例中找到了其他东西。我不认为这会很重要,但很遗憾。所以上面的 mysize 实际上并没有那么大(大约 30-50),但是其中有很多,而且它们都是连续完成的。在这种情况下,三元表达式比 SSE 更快。但是,如果将其反转为 mysize 以百万为单位并且只有 30-50 次迭代,则 SSE 版本更快。知道为什么吗?我认为两者的内存交互是相同的,包括先发制人的预取等......
【问题讨论】:
-
SSE 版本实际上是否比原始版本更平行?
-
串行依赖是在这里杀死你的原因——它使循环不适合 SIMD 矢量化,因此你在 SIMD 循环中做了很多工作。专注于优化标量循环可能会更有成效:确保您使用无分支 min 没有任何不必要的 floatdouble 转换,并且可能还手动展开循环(当然要小心依赖关系)。跨度>
-
分析器错误。该循环不可矢量化 - 除非您尝试类似并行前缀 min.
-
如果您知道
buf[i]在大多数情况下都小于buf[i-1],那么您可能可以使用_mm_movemask_epi8来加快速度。 -
感谢您的回复。这些值可以是任何东西。关于无分支分钟,我也没有成功。我在 x64 上执行此操作,上面的简单三元表达式无法编译为无分支最小值,编译代码中有跳转(VS2010)。有什么建议如何强制它在 x64 上无分支?
标签: optimization floating-point x86 sse simd