C++ 平方差和的SSE优化

C++ 平方差和的SSE优化,c++,image-processing,optimization,sse,simd,C++,Image Processing,Optimization,Sse,Simd,我最近发现,我的程序在以下简单功能上花费的时间最多: void SumOfSquaredDifference( const uint8_t * a, size_t aStride, const uint8_t * b, size_t bStride, size_t width, size_t height, uint64_t * sum) { *sum = 0; for(size_t row = 0; row < height; ++row) {

我最近发现,我的程序在以下简单功能上花费的时间最多:

void SumOfSquaredDifference(
    const uint8_t * a, size_t aStride, const uint8_t * b, size_t bStride, 
    size_t width, size_t height, uint64_t * sum)
{
    *sum = 0;
    for(size_t row = 0; row < height; ++row)
    {
        int rowSum = 0;
        for(size_t col = 0; col < width; ++col)
        {
            int d = a[col] - b[col];
            rowSum += d*d;
        }
        *sum += rowSum;
        a += aStride;
        b += bStride;
    }
}
void sumof平方差(
const uint8_t*a,跨骑尺寸,const uint8_t*b,跨骑尺寸,
尺寸(宽度、尺寸高度、uint64*sum)
{
*总和=0;
用于(行大小=0;行<高度;++行)
{
int rowSum=0;
用于(大小列=0;列<宽度;++列)
{
int d=a[col]-b[col];
行和+=d*d;
}
*总和+=行总和;
a+=跨坐;
b+=b;
}
}
此函数用于查找两个8位灰度图像的平方差之和。 我认为使用SSE可以提高其性能,但我在这方面没有经验。
有人能帮我吗?

当然,你可以改进你的代码。 这是使用SSE2优化函数的一个示例:

const __m128i Z = _mm_setzero_si128();
const size_t A = sizeof(__m128i);

inline __m128i SquaredDifference(__m128i a, __m128i b)
{
    const __m128i aLo = _mm_unpacklo_epi8(a, Z);
    const __m128i bLo = _mm_unpacklo_epi8(b, Z);
    const __m128i dLo = _mm_sub_epi16(aLo, bLo);

    const __m128i aHi = _mm_unpackhi_epi8(a, Z);
    const __m128i bHi = _mm_unpackhi_epi8(b, Z);
    const __m128i dHi = _mm_sub_epi16(aHi, bHi);

    return _mm_add_epi32(_mm_madd_epi16(dLo, dLo), _mm_madd_epi16(dHi, dHi));
}

inline __m128i HorizontalSum32(__m128i a)
{
    return _mm_add_epi64(_mm_unpacklo_epi32(a, Z), _mm_unpackhi_epi32(a, Z));
}

inline uint64_t ExtractSum64(__m128i a)
{
    uint64_t  _a[2];
    _mm_storeu_si128((__m128i*)_a, a);
    return _a[0] + _a[1];
}

void SumOfSquaredDifference(
    const uint8_t *a, size_t aStride, const uint8_t *b, size_t bStride, 
    size_t width, size_t height, uint64_t * sum)
{
    assert(width%A == 0 && width < 0x10000);
    __m128i fullSum = Z;
    for(size_t row = 0; row < height; ++row)
    {
        __m128i rowSum = Z;
        for(size_t col = 0; col < width; col += A)
        {
            const __m128i a_ = _mm_loadu_si128((__m128i*)(a + col));
            const __m128i b_ = _mm_loadu_si128((__m128i*)(b + col)); 
            rowSum = _mm_add_epi32(rowSum, SquaredDifference(a_, b_));
        }
        fullSum = _mm_add_epi64(fullSum, HorizontalSum32(rowSum));
        a += aStride;
        b += bStride;
    }
    *sum = ExtractSum64(fullSum);
}
神奇的描述(请参阅):


GCC有一些开关,鼓励它对代码进行矢量化。例如,
-mfma
开关在这样的简单环路上使用双倍开关,速度提高了约25%。我想使用8位整数会更好。我更喜欢手工编写的优化,因为您的代码保持可读性

也就是说,有一些老把戏可以加速循环:

  • 不要索引,在每次循环迭代中递增指针。在外循环中这样做,在内循环中也应该这样做。您可以在进入内部循环之前创建一个新指针,以便
    +=stride
    保持有效

  • 不要分配给循环中的sum指针,使用局部变量进行累加,完成后复制到输出。可以使用
    行和
    ,但只能在内部循环中使用。在两个循环中使用该变量


在手动优化之前,需要检查编译器是否尚未生成矢量化代码。确保已启用优化和SIMD,并检查生成的代码中的SSE指令,否则您可能会浪费时间做傻事。(顺便说一句,请指定您使用的CPU硬件、操作系统和编译器。)您的外部循环似乎没有用。这是一个错误吗?@a.S.H:我知道这看起来很奇怪,但他在每行迭代中都会碰到
a
b
指针。Clang和GCC会自动将其矢量化,但不是以一种好的方式(使用吞吐量低的
vpmulld
)我希望编译器在使用SSE时有所犹豫<代码>高度、
宽度
跨栏
bStride
都是运行时参数。这意味着每行的开始和结束都不能针对SSE(16字节)进行适当对齐。SSSE3版本中发生了什么?感谢库链接!太棒了!它很接近我要找的东西。
const __m128i K_1FF = _mm_set1_epi16(0x1FF);

inline __m128i SquaredDifference(__m128i a, __m128i b)
{
    const __m128i lo = _mm_maddubs_epi16(_mm_unpacklo_epi8(a, b), K_1FF);
    const __m128i hi = _mm_maddubs_epi16(_mm_unpackhi_epi8(a, b), K_1FF);
    return _mm_add_epi32(_mm_madd_epi16(lo, lo), _mm_madd_epi16(hi, hi));
}
K_1FF -> {-1, 1, -1, 1, ...};
_mm_unpacklo_epi8(a, b) -> {a0, b0, a1, b1, ...};
_mm_maddubs_epi16(_mm_unpacklo_epi8(a, b), K_1FF) -> {b0 - a0, b1 - a1, ...};