最近一直沉迷于SSE方面的优化,实在找不到想学习的参考资料了,就拿个笔记本放在腿上翻翻OpenCv的源代码,无意中看到了OpenCv中关于积分图的代码,仔细研习了一番,觉得OpenCv对SSE的灵活运用真的做的很好,这里记录下我对该段代码的品味并将其思路扩展到其他通道数的图像。

该核心代码位于:Opencv 3.0\opencv\sources\modules\imgproc\src\sumpixels.cpp文件中。

我们贴出最感兴趣的一部分代码以便分析:

    bool operator()(const uchar * src, size_t _srcstep,int * sum, size_t _sumstep,double * sqsum, size_t, int * tilted, size_t,Size size, int cn) const
{
if (sqsum || tilted || cn != || !haveSSE2) return false;
// the first iteration
memset(sum, , (size.width + ) * sizeof(int));
__m128i v_zero = _mm_setzero_si128(), prev = v_zero;
int j = ;
// the others
for (int i = ; i < size.height; ++i)
{
const uchar * src_row = src + _srcstep * i;
int * prev_sum_row = (int *)((uchar *)sum + _sumstep * i) + ;
int * sum_row = (int *)((uchar *)sum + _sumstep * (i + )) + ;
sum_row[-] = ;
prev = v_zero;
j = ;
for ( ; j + < size.width; j += )
{
__m128i vsuml = _mm_loadu_si128((const __m128i *)(prev_sum_row + j));
__m128i vsumh = _mm_loadu_si128((const __m128i *)(prev_sum_row + j + ));
__m128i el8shr0 = _mm_loadl_epi64((const __m128i *)(src_row + j));
__m128i el8shr1 = _mm_slli_si128(el8shr0, );
__m128i el8shr2 = _mm_slli_si128(el8shr0, );
__m128i el8shr3 = _mm_slli_si128(el8shr0, );
vsuml = _mm_add_epi32(vsuml, prev);
vsumh = _mm_add_epi32(vsumh, prev);
__m128i el8shr12 = _mm_add_epi16(_mm_unpacklo_epi8(el8shr1, v_zero),
_mm_unpacklo_epi8(el8shr2, v_zero));
__m128i el8shr03 = _mm_add_epi16(_mm_unpacklo_epi8(el8shr0, v_zero),
_mm_unpacklo_epi8(el8shr3, v_zero));
__m128i el8 = _mm_add_epi16(el8shr12, el8shr03);
__m128i el4h = _mm_add_epi16(_mm_unpackhi_epi16(el8, v_zero),
_mm_unpacklo_epi16(el8, v_zero));
vsuml = _mm_add_epi32(vsuml, _mm_unpacklo_epi16(el8, v_zero));
vsumh = _mm_add_epi32(vsumh, el4h);
_mm_storeu_si128((__m128i *)(sum_row + j), vsuml);
_mm_storeu_si128((__m128i *)(sum_row + j + ), vsumh);
prev = _mm_add_epi32(prev, _mm_shuffle_epi32(el4h, _MM_SHUFFLE(, , , )));
}
for (int v = sum_row[j - ] - prev_sum_row[j - ]; j < size.width; ++j)
sum_row[j] = (v += src_row[j]) + prev_sum_row[j];
}

为了说明更方便,这里贴出我做的普通C语言的代码和重新优化后的SSE代码。

普通C语言:

 void GetGrayIntegralImage(unsigned char *Src, int *Integral, int Width, int Height, int Stride)
{
memset(Integral, , (Width + ) * sizeof(int)); // 第一行都为0
for (int Y = ; Y < Height; Y++)
{
unsigned char *LinePS = Src + Y * Stride;
int *LinePL = Integral + Y * (Width + ) + ;  // 上一行位置
int *LinePD = Integral + (Y + ) * (Width + ) + ; // 当前位置,注意每行的第一列的值都为0
LinePD[-] = ; // 第一列的值为0
for (int X = , Sum = ; X < Width; X++)
{
Sum += LinePS[X]; // 行方向累加
LinePD[X] = LinePL[X] + Sum; // 更新积分图
}
}
}

优化后的SSE算法:

void GetGrayIntegralImage(unsigned char *Src, int *Integral, int Width, int Height, int Stride)
{
memset(Integral, , (Width + ) * sizeof(int)); // 第一行都为0
int BlockSize = , Block = Width / BlockSize;
for (int Y = ; Y < Height; Y++)
{
unsigned char *LinePS = Src + Y * Stride;
int *LinePL = Integral + Y * (Width + ) + ; // 上一行位置
int *LinePD = Integral + (Y + ) * (Width + ) + ; // 当前位置,注意每行的第一列的值都为0
LinePD[-] = ;
__m128i PreV = _mm_setzero_si128();
__m128i Zero = _mm_setzero_si128();
for (int X = ; X < Block * BlockSize; X += BlockSize)
{
__m128i Src_Shift0 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i *)(LinePS + X)), Zero); // A7 A6 A5 A4 A3 A2 A1 A0
__m128i Src_Shift1 = _mm_slli_si128(Src_Shift0, ); // A6 A5 A4 A3 A2 A1 A0 0
__m128i Src_Shift2 = _mm_slli_si128(Src_Shift1, ); // 移位改成基于Shift0,速度慢,Why? // A5 A4 A3 A2 A1 A0 0 0
__m128i Src_Shift3 = _mm_slli_si128(Src_Shift2, ); // A4 A3 A2 A1 A0 0 0 0
__m128i Shift_Add12 = _mm_add_epi16(Src_Shift1, Src_Shift2); // A6+A5 A5+A4 A4+A3 A3+A2 A2+A1 A1+A0 A0+0 0+0
__m128i Shift_Add03 = _mm_add_epi16(Src_Shift0, Src_Shift3); // A7+A4 A6+A3 A5+A2 A4+A1 A3+A0 A2+0 A1+0 A0+0
__m128i Low = _mm_add_epi16(Shift_Add12, Shift_Add03); // A7+A6+A5+A4 A6+A5+A4+A3 A5+A4+A3+A2 A4+A3+A2+A1 A3+A2+A1+A0 A2+A1+A0+0 A1+A0+0+0 A0+0+0+0
__m128i High = _mm_add_epi32(_mm_unpackhi_epi16(Low, Zero), _mm_unpacklo_epi16(Low, Zero)); // A7+A6+A5+A4+A3+A2+A1+A0 A6+A5+A4+A3+A2+A1+A0 A5+A4+A3+A2+A1+A0 A4+A3+A2+A1+A0
__m128i SumL = _mm_loadu_si128((__m128i *)(LinePL + X + ));
__m128i SumH = _mm_loadu_si128((__m128i *)(LinePL + X + ));
SumL = _mm_add_epi32(SumL, PreV);
SumL = _mm_add_epi32(SumL, _mm_unpacklo_epi16(Low, Zero));
SumH = _mm_add_epi32(SumH, PreV);
SumH = _mm_add_epi32(SumH, High);
PreV = _mm_add_epi32(PreV, _mm_shuffle_epi32(High, _MM_SHUFFLE(, , , )));
_mm_storeu_si128((__m128i *)(LinePD + X + ), SumL);
_mm_storeu_si128((__m128i *)(LinePD + X + ), SumH);
}
for (int X = Block * BlockSize, V = LinePD[X - ] - LinePL[X - ]; X < Width; X++)
{
V += LinePS[X];
LinePD[X] = V + LinePL[X];
}
}

  我们先来解释下这段代码的SSE优化过程吧。

首先,用_mm_loadl_epi64一次性加载8个字节数据到XMM寄存器中,其中寄存器的高8位位0,此时寄存器的数据为:

高位            0  0  0  0  0  0  0  0 A7 A6 A5 A4 A3 A2 A1 A0        低位   (8位)

因为涉及到加法,并且最大为8个字节数据的加法,因此转换到16位数据类型,使用_mm_unpacklo_epi8结合zero即可实现。

此时XMM寄存器内容变为:

           Src_Shift0    A7 A6 A5 A4 A3 A2 A1 A0    (16位)

此后有3次移位分别得到:

            Src_Shift1    A6 A5 A4 A3 A2 A1 A0 0       (16位)
Src_Shift2 A5 A4 A3 A2 A1 A0 0 0     (16位)
Src_Shift3 A4 A3 A2 A1 A0 0 0 0 (16位) 通过_mm_add_epi16分别对4组16位数据进行8次相加:
            Shift_Add12   A6+A5 A5+A4 A4+A3 A3+A2 A2+A1 A1+A0 A0+0  0+0   (16位)
Shift_Add03   A7+A4 A6+A3 A5+A2 A4+A1 A3+A0 A2+0 A1+0 A0+0 (16位)
  再对他们进行相加:
        Low            A7+A6+A5+A4 A6+A5+A4+A3 A5+A4+A3+A2 A4+A3+A2+A1 A3+A2+A1+A0 A2+A1+A0+0 A1+A0+0+0 A0+0+0+0

注意到低4位的16位数已经是连续相加的数据了,只要将他们转换为32位就可以直接使用。

而通过 __m128i High = _mm_add_epi32(_mm_unpackhi_epi16(Low, Zero), _mm_unpacklo_epi16(Low, Zero)); 这一句则可以把前面的高4位连续相加的值拼接起来得到:

       High                  A7+A6+A5+A4+A3+A2+A1+A0  A6+A5+A4+A3+A2+A1+A0  A5+A4+A3+A2+A1+A0  A4+A3+A2+A1+A0

  后面的操作则顺理成章了。

注意到我核心的改动在于原始代码中的el8shr12和el8shr03的计算中的_mm_unpacklo_epi8被消除了,而在el8shr0一句中增加了一个_mm_unpacklo_epi8,因此少了3次这个函数,很明显这样做是不会改变计算结果的。

另外源代码中的部分_mm_add_epi16被我用_mm_add_epi32代替了,这主要是因为用_mm_add_epi32意义更明显,而且由于高位数据为0,他们的执行结果不会有任何区别。

   还有一点在测试时发现,如果Src_Shift2,Src_Shift3的移位是基于Src_Shift0,即使用如下代码:

__m128i Src_Shift2 = _mm_slli_si128(Src_Shift0, );
__m128i Src_Shift3 = _mm_slli_si128(Src_Shift0, );

   速度会有较为明显的下降,难道说移动的位数多少和CPU的耗时有关?

以上是灰度模式的算法,在我的笔记本电脑上,SSE优化后的语句虽然增加了很多,但是执行效率约能提升30%,不过在一些PC上,普通的C和SSE优化后却没有啥速度区别了,这也不知道是为什么了。

如果是针对24位或者32位图像,基本的优化思想是一致的,不过有更多的细节需要自己注意。

24位或者32位图像在任何机器配置上,速度都能有30%的提升的。

还是感觉这种算法用文字很难表述清楚,用代码再加上自己的空间组合可能更能理解吧。

最新文章

  1. STL中vector、list、map、set区别(转载)
  2. 【bzoj1911】 Apio2010—特别行动队
  3. java正则表达式 --简单认识
  4. WCF绑定类型选择
  5. 访问图片可以使用闭包map
  6. linux 的终端字体色和背景色的修改方法(三)
  7. spring常用的工具类
  8. jmeter测试手机app
  9. yii CListView中使用CArrayDataProvider自定义数组作为数据
  10. Android 关于调用系统内已安装的相机问题
  11. Ip 讲解
  12. POJ 2125 Destroying The Graph 二分图 最小点权覆盖
  13. Python学习一:Python简介
  14. [Android] AutoCompleteTextView:自己主动完毕输入内容的控件(自己主动补全)
  15. Java之排序
  16. temp 和 tmp 文件
  17. 服务器上部署Struts2的web项目报struts-default.xml:131:154的解决方法
  18. java.sql.SQLException: The SQL statement must not be null or empty.这个错误
  19. 莫烦keras学习自修第五天【CNN卷积神经网络】
  20. 【PAT】B1041 考试座位号(15 分)

热门文章

  1. js下拉列表
  2. .net core cookie登录和session的 DataProtectionProvider 加入 redis
  3. Python中元类
  4. Python replace() 和 re.sub() 字符串字符替换
  5. 爬虫2 urllib3 爬取30张百度图片
  6. DFS Tempter of the Bone
  7. Windows10 下Apache服务器搭建
  8. hdu1598 find the most comfortable road (枚举)+【并查集】
  9. Windows上搭建安卓的Java开发环境
  10. LOJ.6074.[2017山东一轮集训Day6]子序列(DP 矩阵乘法)