C++ 两个8位阵列协方差的快速实现

C++ 两个8位阵列协方差的快速实现,c++,image-processing,optimization,sse,simd,C++,Image Processing,Optimization,Sse,Simd,我需要比较大量小尺寸的类似图像(高达200x200)。 因此,我尝试实现SSIM(Structural similarity see)算法。 SSIM需要计算两个8位灰度图像的协方差。 一个简单的实现如下所示: float SigmaXY(const uint8_t * x, const uint8_t * y, size_t size, float averageX, float averageY) { float sum = 0; for(size_t i = 0; i &l

我需要比较大量小尺寸的类似图像(高达200x200)。 因此,我尝试实现SSIM(Structural similarity see)算法。 SSIM需要计算两个8位灰度图像的协方差。 一个简单的实现如下所示:

float SigmaXY(const uint8_t * x, const uint8_t * y, size_t size, float averageX, float averageY)
{
    float sum = 0;
    for(size_t i = 0; i < size; ++i)
        sum += (x[i] - averageX) * (y[i] - averageY);
    return sum / size;
}
float SigmaXY(常量uint8\u t*x,常量uint8\u t*y,大小\u t size,float averageX,float averageY)
{
浮点数和=0;
对于(大小i=0;i
但它的性能很差。 所以我希望通过使用SIMD或CUDA来改进它(我听说这是可以做到的)。 不幸的是,我没有这样做的经验。
看起来怎么样?我要去哪里?

算法有一个SIMD实现(我使用了SSE4.1):

#包括
模板内联函数m128 SigmaXY(常数m128i&x、常数m128i&y、常数m128&averageX、常数m128&averageY)
{
__m128(x=(x,shift));
__m128 _y=_mm_cvtepi32_ps(_mm_cvtepu8_epi32(_mm_srli_si128(y,shift)));
返回多个ps((x,averageX),(y,averageY))
}
浮点SigmaXY(常量uint8_t*x,常量uint8_t*y,大小大小,浮点平均值x,浮点平均值y)
{
浮点数和=0;
尺寸i=0,对齐尺寸=尺寸/16*16;
如果(尺寸>=16)
{
__m128总和=_mm_setzero_ps();
__m128 avgX=_mm_set1_ps(averageX);
__m128平均值=_mm_set1_ps(平均值);
对于(;i

我希望它对您有用。

我还有一个很好的解决方案

首先我想提到一些数学公式:

averageX = Sum(x[i])/size;
averageY = Sum(y[i])/size;
因此:

Sum((x[i] - averageX)*(y[i] - averageY))/size = 

Sum(x[i]*y[i])/size - Sum(x[i]*averageY)/size - 
Sum(averageX*y[i])/size + Sum(averageX*averageY)/size = 

Sum(x[i]*y[i])/size - averageY*Sum(x[i])/size - 
averageX*Sum(y[i])/size + averageX*averageY*Sum(1)/size =   

Sum(x[i]*y[i])/size - averageY*averageX - 
averageX*averageY + averageX*averageY = 

Sum(x[i]*y[i])/size - averageY*averageX;     
它允许修改我们的算法:

float SigmaXY(const uint8_t * x, const uint8_t * y, size_t size, float averageX, float averageY)
{
    uint32_t sum = 0; // If images will have size greater then 256x256 than you have to use uint64_t.
    for(size_t i = 0; i < size; ++i)
        sum += x[i]*y[i];
    return sum / size - averageY*averageX;
} 
float SigmaXY(常量uint8\u t*x,常量uint8\u t*y,大小\u t size,float averageX,float averageY)
{
uint32\u t sum=0;//如果图像的大小大于256x256,则必须使用uint64\u t。
对于(大小i=0;i
只有在这之后,我们才能使用SIMD(我使用SSE2):

#包括
直列式m128i SigmaXY(uuum128i x,uuum128i y)
{
__m128i lo=_mm_madd_epi16(_mm_unplo_epi8(x,_mm_setzero_si128()),_mm_unplo_epi8(y,_mm_setzero_si128());
__m128i hi=_mm_madd_epi16(_mm_unpachi_epi8(y,_mm_setzero_si128()),_mm_unpachi_epi8(y,_mm_setzero_si128());
返回(低,高)添加(低,高);;
}
浮点SigmaXY(常量uint8_t*x,常量uint8_t*y,大小大小,浮点平均值x,浮点平均值y)
{
uint32_t sum=0;
尺寸i=0,对齐尺寸=尺寸/16*16;
如果(尺寸>=16)
{
__m128i和=_mm_setzero_si128();
对于(;i
Wow,在循环结束之前你不需要平均值,这真是太奇怪了。这意味着你也可以动态计算平均值。可以使用
psadbw
,也可以使用
\u mm\u add\u epi16
,当你为
madd
解包时使用它。当然,可以计算协方差和一秒和秒的矩同一循环中的ond顺序。您是否允许编译器进行积极优化(例如,gcc上的
-funsafe数学优化
-Ofast
)?如果你不这样做,编译器就不能对代码做太多的处理,因为它不能矢量化,因为浮点数学没有关联性。很好的一点:使用
-O3-ffast math-march=haswell
,它使用
pmovzx
加载和FMA,并利用AVX2使用256b向量。godbolt链接有一些改进,比如e一个更有效的水平总和。(我很久以前就开始了这个评论,在你发布另一个答案之前)。使用
\u mm\u cvtepu8\u epi32
PMOVZX
)可能会更好作为一个负载,它不是移动全向量负载的结果,但这很难用内部函数实现。当输入指针是16B对齐时,它应该不会有太大的区别。gcc甚至会在可用时使用FMA,将
SigmaXY
中的
mul\u ps
add\u ps
组合到累加器中。使用多个累加器可能有助于允许更多的独立工作重叠:因为与计算独立
SigmaXY
结果的吞吐量相比,
总和的循环携带依赖性并不短。此外,水平总和可以更有效地完成:@Peter这不重要,因为有一个最佳解决方案不使用浮点数。@ErmIg:同意。在发布整数版本之前,我已经输入了大部分浮点数,但在发布最优水平添加之前,我得到了旁注。我最终决定将其作为注释发布,而不仅仅是放弃,因为任何从这个答案中学到任何东西的人都可能从看到这些注释中受益。
float SigmaXY(const uint8_t * x, const uint8_t * y, size_t size, float averageX, float averageY)
{
    uint32_t sum = 0; // If images will have size greater then 256x256 than you have to use uint64_t.
    for(size_t i = 0; i < size; ++i)
        sum += x[i]*y[i];
    return sum / size - averageY*averageX;
} 
#include <emmintrin.h>

inline __m128i SigmaXY(__m128i x, __m128i y)
{
    __m128i lo = _mm_madd_epi16(_mm_unpacklo_epi8(x, _mm_setzero_si128()), _mm_unpacklo_epi8(y, _mm_setzero_si128()));
    __m128i hi = _mm_madd_epi16(_mm_unpackhi_epi8(y, _mm_setzero_si128()), _mm_unpackhi_epi8(y, _mm_setzero_si128()));
    return _mm_add_epi32(lo, hi);
}

float SigmaXY(const uint8_t * x, const uint8_t * y, size_t size, float averageX, float averageY)
{
    uint32_t sum = 0;
    size_t i = 0, alignedSize = size/16*16;
    if(size >= 16)
    {
        __m128i sums = _mm_setzero_si128();
        for(; i < alignedSize; i += 16)
        {
            __m128i _x = _mm_loadu_si128((__m128i*)(x + i));
            __m128i _y = _mm_loadu_si128((__m128i*)(y + i));
            sums = _mm_add_epi32(sums, SigmaXY(_x, _y));
        }
        uint32_t _sums[4];
        _mm_storeu_si128(_sums, sums);
        sum = _sums[0] + _sums[1] + _sums[2] + _sums[3];
    } 
    for(; i < size; ++i)
        sum += x[i]*y[i];
    return sum / size - averageY*averageX;
}