优化SSE代码

时间:2013-07-03 15:26:05

标签: c++ optimization sse

我想帮助优化我的程序中计算量最大的功能。 目前,我发现基本(非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的剩余计算中添加,但是在性能提高之前这样做的目的不大,而且此时只会使代码混乱。

感谢您提供任何帮助。

2 个答案:

答案 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指令会导致整体指令,并且编译器无法很好地优化生成的代码。

有一些替代策略可以改善带宽限制问题的性能:

  • 多线程通过并发算法隐藏访问内存 超线程上下文中的操作。
  • 精确调整数据负载大小可以提高内存带宽。
  • 通过在循环中添加补充的独立运算来提高管道连续性(在“for”循环的每一步扫描两组不同的数据)
  • 在缓存或寄存器中保留更多数据(代码的某些迭代可能需要多次同一组数据)