5

我编写了 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);
4

3 回答 3

6

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

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

基本上想象你传入一个 nLength 120 的数组。你在第一次运行时很好。i 现在等于 64。 i < 120 所以你做另一个循环。不幸的是,您在到达 128 之前通过了数组的末尾,并且您进入了未定义的行为领域。这可能表现为访问冲突 (0xC0000005),会导致您崩溃。

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

于 2013-03-19T21:31:59.070 回答
4

根据要求,当我说“循环中有 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;
}
于 2013-03-20T10:02:33.967 回答
0

我自己只是在研究这种东西,我发现哈罗德的回答对我现在正在做的类似事情很有帮助。但是,我想提出的另一个建议是,如果对您的应用程序来说不是太麻烦,您是否考虑过在最后用零填充数据,这样您就不需要在最后执行这些额外的工作你的功能?

于 2013-08-13T07:51:07.060 回答