【问题标题】:SSE code for sum bytes. Where's the bug?总字节数的 SSE 代码。错误在哪里?
【发布时间】:2013-03-19 21:15:39
【问题描述】:

我编写了 SSE 代码来汇总字节值。 (VS2005.)

因为它足够简单,所以效果很好(而且速度很快)。只有某些大小的数组会发生崩溃。它只在发布模式下崩溃 - 在调试中永远不会。也许有人看到了“明显”的错误? 任何帮助表示赞赏。

__int64 Sum (const unsigned char* pData, const unsigned int& nLength)
{
    __int64 nSum (0);

    __m128i* pp = (__m128i*)pData;

    ATLASSERT( ( (DWORD)pp & 15 ) == 0 ); // pointer must point to address multiple of 16 (cache line)

    __m128i zero = _mm_setzero_si128(),
        a, b, c, d, tmp;

    unsigned int i (0);

    for ( ; i < nLength; i+=64) // 4-fach loop-unroll (x 16)
    {
        a = _mm_sad_epu8( *(pp++), zero);           
        b = _mm_sad_epu8( *(pp++), zero);  // It crashes here.
        c = _mm_sad_epu8( *(pp++), zero);
        d = _mm_sad_epu8( *(pp++), zero);

        // commenting the following line prevents the crash (???)
        tmp = _mm_add_epi64( _mm_add_epi64( _mm_add_epi64( a, b ), c ), d);

        a = _mm_srli_si128 ( tmp, 8 );

        nSum += _mm_cvtsi128_si32( a ) + _mm_cvtsi128_si32( tmp );
    }

    // ... the rest
    if (nLength % 64)
        for (i -= 64; i < nLength; i++)
            nSum += pData [i];

    return nSum;
}

函数是这样调用的:

unsigned int nLength = 3571653;  // One of the values that causes crash
unsigned char *pData = (unsigned char*) _aligned_malloc(nLength, 16);
Sum (pData,  nLength);

【问题讨论】:

  • 嗯,你检查过pp 还在范围内吗?
  • 检查调试和发布模式之间的程序集差异,因为发布模式优化可能破坏了您所做的假设。
  • 这对其他人来说是不是过早的优化?
  • @modifiablelvalue no?
  • @harold 编译器可能会自动为你做这件事吗?即使在没有自动完成优化的情况下,总结一组unsigned shorts 似乎太可能成为瓶颈?这对缓存很友好;也许还有其他对缓存不太友好的领域值得更高优先级关注;)

标签: c algorithm visual-studio-2005 sse


【解决方案1】:

你的for循环需要定义如下:

for ( ; i < (nLength - 63); i+=64)

基本上想象你传入一个 nLength 为 120 的数组。你在第一次运行时没问题。 i 现在等于 64。 i

现在以 nLength=128 为例,根据我建议的修改,它应该可以在您的优化循环中完美运行。第一个循环 i 很好,并且 i = 64。i 小于 65,因此执行另一个循环。 i 现在等于 128 并且循环退出。外部循环也不会运行,因为 i == nLength。工作完成:)

【讨论】:

  • 就是这样 - 谢谢! :-) 虽然对我和我的第一篇文章来说有点尴尬。通过上述修复,“if (nLength % 64)”就不再必要了。并且代码速度大约是没有 SSE (@VS2005) 的 7 倍。
  • 然后是第二个循环: // ...其余的 for ( ; i
  • @nji9 您是否尝试过在循环中使用 4 个累加器并在循环后对它们求和?在我的 Core2 上比这快很多。
  • @harold 哇——好主意!唯一的问题:哪种类型的蓄能器?因为你已经注意溢出了。我可以请您发布完整的代码,以便对它感兴趣的人不必从修复和建议中将其拼凑在一起吗?问候。 :)
【解决方案2】:

根据要求,当我说“循环中有 4 个累加器并在循环后对它们求和”时,这就是我的想法。

__int64 Sum (const unsigned char* pData, const int& nLength)
{
    __int64 nSum (0);

    __m128i* pp = (__m128i*)pData;


    __m128i zero = _mm_setzero_si128(),
        a = _mm_setzero_si128(),
        b = _mm_setzero_si128(),
        c = _mm_setzero_si128(),
        d = _mm_setzero_si128(), tmp;

    int i (0);

    for ( ; i < (nLength - 63); i+=64)
    {
        a = _mm_add_epi64( _mm_sad_epu8( *(pp++), zero ), a );
        b = _mm_add_epi64( _mm_sad_epu8( *(pp++), zero ), b );
        c = _mm_add_epi64( _mm_sad_epu8( *(pp++), zero ), c );
        d = _mm_add_epi64( _mm_sad_epu8( *(pp++), zero ), d );
    }

    tmp = _mm_add_epi64( _mm_add_epi64( a, b ), _mm_add_epi64( c, d ));
    tmp = _mm_add_epi64( _mm_srli_si128( tmp, 8 ), tmp );
    nSum = (_mm_cvtsi128_si32( tmp ) & 0xFFFFFFFFULL) + 
               (((__int64)_mm_cvtsi128_si32( _mm_srli_si128( tmp, 4 ) )) << 32);

    // ... the rest
    for (; i < nLength; i++)
        nSum += pData [i];

    return nSum;
}

【讨论】:

  • 太棒了!为了使其适用于 nLength
  • 另一方面,也有一个问题:如果 nLength > 2^23+2^15 (about) 某处一定有溢出。不知道为什么和在哪里(在累加器中?)。而“我的旧”版本可以正常工作到 INT_MAX。这个限制 (2^23) 是一个严重的限制,因为作为图像它是 8 兆像素。
  • 好了,溢出修复了。在 x64 上会容易得多,您可以在那里用_mm_cvtsi128_si64 提取总和。
【解决方案3】:

我自己也刚刚涉足这类事情,我发现harold's answer 对我现在正在做的类似事情非常有帮助。但是,我想提出的另一个建议是,如果对您的应用程序来说不是太麻烦,您是否考虑过在最后用零填充数据,这样您就不需要在最后执行这些额外的工作你的职能?

【讨论】:

    猜你喜欢
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-15
    相关资源
    最近更新 更多