我想帮助优化我的程序中计算量最大的功能。 目前,我发现基本(非SSE)版本明显更快(最多3倍)。因此,我会请求你帮助纠正这个问题。
该函数在无符号整数向量中查找子集,并报告它们是否存在。为方便起见,我仅提供了相关的代码段。
首先是基本变体。它检查blocks_是否是x.blocks _的子集。
//Check for self comparison
if (this == &x)
return false;
//A subset is equal to or smaller.
if (no_bits_ > x.no_bits_)
return false;
int i;
bool equal = false;
//Pointers should not change.
const unsigned int *tptr = blocks_;
const unsigned int *xptr = x.blocks_;
for (i = 0; i < no_blocks_; i++, tptr++, xptr++) {
if ((*tptr & *xptr) != *tptr)
return false;
if (*tptr != *xptr)
equal = true;
}
return equal;
然后出现了SSE变体,唉根据我的期望不能表现。这两个片段都应该寻找相同的东西。
//starting pointers.
const __m128i* start = (__m128i*)&blocks_;
const __m128i* xstart = (__m128i*)&x.blocks_;
__m128i block;
__m128i xblock;
//Unsigned ints are 32 bits, meaning 4 can fit in a register.
for (i = 0; i < no_blocks_; i+=4) {
block = _mm_load_si128(start + i);
xblock = _mm_load_si128(xstart + i);
//Equivalent to (block & xblock) != block
if (_mm_movemask_epi8(_mm_cmpeq_epi32(_mm_and_si128(block, xblock), block)) != 0xffff)
return false;
//Equivalent to block != xblock
if (_mm_movemask_epi8(_mm_cmpeq_epi32(block, xblock)) != 0xffff)
equal = true;
}
return equal;
对于如何改进SSE版本的性能,您有什么建议吗?难道我做错了什么?或者这是否应该在其他地方进行优化?
我还没有在no_blocks_%4!= 0的剩余计算中添加,但是在性能提高之前这样做的目的不大,而且此时只会使代码混乱。
感谢您提供任何帮助。
答案 0 :(得分:3)
我在这里看到三种可能性。
首先,您的数据可能不适合广泛的比较。如果前几个块中(*tptr & *xptr) != *tptr
的可能性很高,那么普通的C ++版本几乎肯定会更快。在这种情况下,您的SSE将运行更多代码和数据来完成同样的事情。
其次,您的SSE代码可能不正确。这里还不完全清楚。如果no_blocks_
在两个样本之间相同,则start + i
可能具有索引到128位元素的不需要的行为,而不是32位作为第一个样本。
第三,当指令可以流水线化时,SSE 真的喜欢它,这是一个很短的循环,你可能没有得到它。您可以通过一次处理多个SSE块来显着减少分支。
这是一次快速未经测试的处理2个SSE块的镜头。注意我已经完全删除了block != xblock
分支,方法是将状态保持在循环之外,并且仅在结束时进行测试。总的来说,这会将每个int
的1.3个分支移动到0.25。
bool equal(unsigned const *a, unsigned const *b, unsigned count)
{
__m128i eq1 = _mm_setzero_si128();
__m128i eq2 = _mm_setzero_si128();
for (unsigned i = 0; i != count; i += 8)
{
__m128i xa1 = _mm_load_si128((__m128i const*)(a + i));
__m128i xb1 = _mm_load_si128((__m128i const*)(b + i));
eq1 = _mm_or_si128(eq1, _mm_xor_si128(xa1, xb1));
xa1 = _mm_cmpeq_epi32(xa1, _mm_and_si128(xa1, xb1));
__m128i xa2 = _mm_load_si128((__m128i const*)(a + i + 4));
__m128i xb2 = _mm_load_si128((__m128i const*)(b + i + 4));
eq2 = _mm_or_si128(eq2, _mm_xor_si128(xa2, xb2));
xa2 = _mm_cmpeq_epi32(xa2, _mm_and_si128(xa2, xb2));
if (_mm_movemask_epi8(_mm_packs_epi32(xa1, xa2)) != 0xFFFF)
return false;
}
return _mm_movemask_epi8(_mm_or_si128(eq1, eq2)) != 0;
}
如果你在前几个SSE模块中有足够的数据和低失败概率,那么这样的事情应该比你的SSE快一些。
答案 1 :(得分:0)
我认为你的问题是内存带宽有限的问题: 渐近你需要大约2个操作来处理扫描内存中的一对整数。没有足够的算术复杂性来利用CPU SSE指令的更多算术吞吐量。实际上,CPU会花费大量时间等待数据传输。 但是在您的情况下使用SSE指令会导致整体指令,并且编译器无法很好地优化生成的代码。
有一些替代策略可以改善带宽限制问题的性能: