优化检查一个比特向量是否是另一个的适当子集
Optimize check for a bit-vector being a proper subset of another?
我想要一些帮助来优化我的程序中计算量最大的函数。目前,我发现基本(非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
的剩余计算,但在性能提高之前,这样做几乎没有什么意义,而且在这一点上只会打乱代码。
我在这里看到了三种可能性。
首先,您的数据可能不适合广泛比较。如果(*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快一些。
我认为您的问题是内存带宽受限的问题:渐近你需要大约2个运算来处理扫描内存中的一对整数。没有足够的算术复杂性来利用CPU SSE指令的更多算术吞吐量。事实上,您的CPU会花费大量时间等待数据传输。但是,在您的情况下使用SSE指令会导致整个指令,编译器并没有很好地优化生成的代码。
有一些可供选择的策略可以提高带宽受限问题的性能:
- 基于并发算法的多线程隐藏访问内存超线程上下文中的操作
- 每次对数据负载大小进行微调可以提高内存带宽
- 通过在循环中添加补充的独立操作来提高管线的连续性(在"for"循环的每个步骤扫描两组不同的数据)
- 在缓存或寄存器中保留更多数据(代码的某些迭代可能多次需要相同的数据集)
- 检查一个类型是否直接派生自"enable if"上下文中的另一个类型(是其子类型)
- 如何检查一个模板是否是另一个模板的类成员
- 是否可以将带有捕获和参数的 lambda 传递给另一个函数?如果是这样,如何?
- 检查数值类型是否是另一个数值类型的子集
- 在另一个对象 B 中创建对象 A 时,对象 A 是否是对象 B 的本地对象,对象 A 是否会存在于对象 B 的实例化之外?
- 如何说一个有效或无效,即使一个是,另一个不是
- 找出std :: type_info对象是否是C 中另一个std :: type_info对象的子类型
- 在一个结构中,使用一个数组字段访问另一个是否合法
- 使用 gtest EXPECT_CALL 时竞争条件段错误,而另一个期望是执行相同的方法
- 是正则是查看字符串是否是另一个弦的好方法
- 检查一个字符串是否是另一个字符串的排列C++
- 如何检查字符串是否是另一个字符串的正确子集
- 如何检查一个范围内的值是否是另一个范围内的值的倍数
- Strchr不起作用,或者我需要一个替代方案来检查给定字符串中的任何字符是否是另一个字符串的一部分
- 如何识别模板参数参数是否是模板内结构中另一个类的实例?C++
- 确定一个向量是否是另一个向量的子集的有效方法
- 一个命名空间是否可以是另一个命名空间的成员
- 用两个向量,一个变量是x, y, z,另一个值是1,2,3,来得到x=1, y=2, z=3的方程
- 一个字符串开始另一个字符串是什么意思?c++
- 另一个选项是双指针