SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。

您所在的位置:网站首页 图像处理的模LOG是什么 SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。

SSE图像算法优化系列十七:多个图像处理中常用函数的SSE实现。

2024-07-13 15:26| 来源: 网络整理| 查看: 265

  在做图像处理的SSE优化时,也会经常遇到一些小的过程、数值优化等代码,本文分享一些个人收藏或实现的代码片段给大家。

一、快速求对数运算

  对数运算在图像处理中也是个经常会遇到的过程,特备是在一些数据压缩和空间转换时常常会用到,而且是个比较耗时的函数,标准的SSE库里并没有提供该函数的实现,如果需要高精度的SSE版本,网络上已经有了,参考:https://github.com/to-miz/sse_mathfun_extension/blob/master/sse_mathfun.h,这个的精度和标准库的精度基本一致了,稍作整理后的代码如下:

// 对数函数的SSE实现,高精度版 inline __m128 _mm_log_ps(__m128 x) { static const __declspec(align(16)) int _ps_min_norm_pos[4] = { 0x00800000, 0x00800000, 0x00800000, 0x00800000 }; static const __declspec(align(16)) int _ps_inv_mant_mask[4] = { ~0x7f800000, ~0x7f800000, ~0x7f800000, ~0x7f800000 }; static const __declspec(align(16)) int _pi32_0x7f[4] = { 0x7f, 0x7f, 0x7f, 0x7f }; static const __declspec(align(16)) float _ps_1[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; static const __declspec(align(16)) float _ps_0p5[4] = { 0.5f, 0.5f, 0.5f, 0.5f }; static const __declspec(align(16)) float _ps_sqrthf[4] = { 0.707106781186547524f, 0.707106781186547524f, 0.707106781186547524f, 0.707106781186547524f }; static const __declspec(align(16)) float _ps_log_p0[4] = { 7.0376836292E-2f, 7.0376836292E-2f, 7.0376836292E-2f, 7.0376836292E-2f }; static const __declspec(align(16)) float _ps_log_p1[4] = { -1.1514610310E-1f, -1.1514610310E-1f, -1.1514610310E-1f, -1.1514610310E-1f }; static const __declspec(align(16)) float _ps_log_p2[4] = { 1.1676998740E-1f, 1.1676998740E-1f, 1.1676998740E-1f, 1.1676998740E-1f }; static const __declspec(align(16)) float _ps_log_p3[4] = { -1.2420140846E-1f, -1.2420140846E-1f, -1.2420140846E-1f, -1.2420140846E-1f }; static const __declspec(align(16)) float _ps_log_p4[4] = { 1.4249322787E-1f, 1.4249322787E-1f, 1.4249322787E-1f, 1.4249322787E-1f }; static const __declspec(align(16)) float _ps_log_p5[4] = { -1.6668057665E-1f, -1.6668057665E-1f, -1.6668057665E-1f, -1.6668057665E-1f }; static const __declspec(align(16)) float _ps_log_p6[4] = { 2.0000714765E-1f, 2.0000714765E-1f, 2.0000714765E-1f, 2.0000714765E-1f }; static const __declspec(align(16)) float _ps_log_p7[4] = { -2.4999993993E-1f, -2.4999993993E-1f, -2.4999993993E-1f, -2.4999993993E-1f }; static const __declspec(align(16)) float _ps_log_p8[4] = { 3.3333331174E-1f, 3.3333331174E-1f, 3.3333331174E-1f, 3.3333331174E-1f }; static const __declspec(align(16)) float _ps_log_q1[4] = { -2.12194440e-4f, -2.12194440e-4f, -2.12194440e-4f, -2.12194440e-4f }; static const __declspec(align(16)) float _ps_log_q2[4] = { 0.693359375f, 0.693359375f, 0.693359375f, 0.693359375f }; __m128 one = *(__m128*)_ps_1; __m128 invalid_mask = _mm_cmple_ps(x, _mm_setzero_ps()); /* cut off denormalized stuff */ x = _mm_max_ps(x, *(__m128*)_ps_min_norm_pos); __m128i emm0 = _mm_srli_epi32(_mm_castps_si128(x), 23); /* keep only the fractional part */ x = _mm_and_ps(x, *(__m128*)_ps_inv_mant_mask); x = _mm_or_ps(x, _mm_set1_ps(0.5f)); emm0 = _mm_sub_epi32(emm0, *(__m128i *)_pi32_0x7f); __m128 e = _mm_cvtepi32_ps(emm0); e = _mm_add_ps(e, one); __m128 mask = _mm_cmplt_ps(x, *(__m128*)_ps_sqrthf); __m128 tmp = _mm_and_ps(x, mask); x = _mm_sub_ps(x, one); e = _mm_sub_ps(e, _mm_and_ps(one, mask)); x = _mm_add_ps(x, tmp); __m128 z = _mm_mul_ps(x, x); __m128 y = *(__m128*)_ps_log_p0; y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p1); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p2); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p3); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p4); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p5); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p6); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p7); y = _mm_mul_ps(y, x); y = _mm_add_ps(y, *(__m128*)_ps_log_p8); y = _mm_mul_ps(y, x); y = _mm_mul_ps(y, z); tmp = _mm_mul_ps(e, *(__m128*)_ps_log_q1); y = _mm_add_ps(y, tmp); tmp = _mm_mul_ps(z, *(__m128*)_ps_0p5); y = _mm_sub_ps(y, tmp); tmp = _mm_mul_ps(e, *(__m128*)_ps_log_q2); x = _mm_add_ps(x, y); x = _mm_add_ps(x, tmp); x = _mm_or_ps(x, invalid_mask); // negative arg will be NAN return x; }

  看上去有一大堆代码,不过实测这个的速度越是标准库(本文是指启动增强指令集选项设置为:未设置,设计上编译器在此种情况下会自动设置为SSE2增强,这可以从反编译logf函数看到,因此,这里的速度比较还不是和纯Fpu实现的比较)的2倍,如果稍微降低点精度,比如_ps_log_p5到_ps_log_p8之间的代码,还能提高点速度。

  另外,在很多场合我们还可以使用另外一种低精度的log函数,其C代码如下所示:

//https://stackoverflow.com/questions/9411823/fast-log2float-x-implementation-c inline float IM_Flog(float val) { union { float val; int x; } u = { val }; float log_2 = (float)(((u.x >> 23) & 255) - 128); u.x &= ~(255 8); // 似乎V可以是负数 }

  翻译为SSE为:

// 返回16位无符号整形数据整除255后四舍五入的结果: x = ((x + 1) + (x >> 8)) >> 8: inline __m128i _mm_div255_epu16(__m128i x) { return _mm_srli_epi16(_mm_adds_epu16(_mm_adds_epu16(x, _mm_set1_epi16(1)), _mm_srli_epi16(x, 8)), 8); }

九、求XMM寄存器内所有元素的累加值

  这也是个常见的需求,我们可能把某个结果重复的结果保存在寄存器中,最后结束时在把寄存器中的每个元素想加,你当然可以通过访问__m128i变量的内部的元素实现,但是据说这样会降低循环内的优化,一种方式是直接用SSE指令实现,比如对8个有符号的short类型的相加代码如下所示:

// 8个有符号的16位的数据相加的和。 // https://stackoverflow.com/questions/31382209/computing-the-inner-product-of-vectors-with-allowed-scalar-values-0-1-and-2-usi/31382878#31382878 inline int _mm_hsum_epi16(__m128i V) // V7 V6 V5 V4 V3 V2 V1 V0 { // V = _mm_unpacklo_epi16(_mm_hadd_epi16(V, _mm_setzero_si128()), _mm_setzero_si128()); 也可以用这句,_mm_hadd_epi16似乎对计算结果超出32768能获得正确结果 __m128i T = _mm_madd_epi16(V, _mm_set1_epi16(1)); // V7+V6 V5+V4 V3+V2 V1+V0 T = _mm_add_epi32(T, _mm_srli_si128(T, 8)); // V7+V6+V3+V2 V5+V4+V1+V0 0 0 T = _mm_add_epi32(T, _mm_srli_si128(T, 4)); // V7+V6+V3+V2+V5+V4+V1+V0 V5+V4+V1+V0 0 0 return _mm_cvtsi128_si32(T); // 提取低位 }

  对于epi32或者ps类型也是使用类似的过程的。

10、求16个字节的最小值

  比如我们要求一个字节序列的最小值,我们肯定会使用_mm_min_epi8这样的函数保存每隔16个字节的最小值,这样最终我们得到16个字节的一个XMM寄存器,整个序列的最小值肯定在这个16个字节里面,这个时候我们可以巧妙的借用下面的SSE语句得到这16个字节的最小值:

// 求16个字节数据的最小值, 只能针对字节数据。 inline int _mm_hmin_epu8(__m128i a) { __m128i L = _mm_unpacklo_epi8(a, _mm_setzero_si128()); __m128i H = _mm_unpackhi_epi8(a, _mm_setzero_si128()); return _mm_extract_epi16(_mm_min_epu16(_mm_minpos_epu16(L), _mm_minpos_epu16(H)), 0); }

  SSE3提供了_mm_minpos_epu16函数,他能获取8个无符号数的的最小值及其最小值的索引,放置在寄存器的低16和低32位,我们把字节数据扩展到16位,然后在通过两次比较就可以获得相应的最小值了。

  那如果是求最大值呢,可惜SSE没有提供_mm_maxpos_epu16函数,但是也无妨,稍微修改下上面的代码就可以了,如下所示:

// 求16个字节数据的最大值, 只能针对字节数据。 inline int _mm_hmax_epu8(__m128i a) { __m128i b = _mm_subs_epu8(_mm_set1_epi8(255), a); __m128i L = _mm_unpacklo_epi8(b, _mm_setzero_si128()); __m128i H = _mm_unpackhi_epi8(b, _mm_setzero_si128()); return 255 - _mm_extract_epi16(_mm_min_epu16(_mm_minpos_epu16(L), _mm_minpos_epu16(H)), 0); }

十一、其他一些优化技巧

  在http://www.alfredklomp.com/programming/sse-intrinsics/ 以及 http://www.itkeyword.com/doc/0326039046115117x827/c++-sse2-intrinsics-comparing-unsigned-integers等网站上还有很多参考的资料,希望大家自己去学习下。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3