用SSE指令集增强浮点运算性能


代码效率优化
1,算法优化/改变算法
2,改变结构,传输数据等方面
3,改变指令(cpu指令集来进行优化)
SSE是很常见的一个X86平台的指令集,早在P4时代就已经出现了。后来INTEL又接连着推出了SSE2,SSE3,SSE4等(不过可没有SSE5,原本规划是有的,后来INTEL独立发展了新一代AVX指令集旨在取代SSE,关于AVX现在资料还不是很多,用的也没有SSE普遍。毕竟支持AVX的CPU也不多,像我的T4400就不支持)。
废话不多说,还是来点实在的东西。大家都知道浮点数运算比起整数运算,速度的确是非常缓慢,很多领域比如图像处理中,需要大量用到浮点数运算,此时CPU就是一个很显著的瓶颈,为了提高浮点数性能,我们有两个方法:
1,化浮点为整形:即尽量通过某种数学变换将原来的浮点数运算变成整数运算。
2,使用SSE这类指令集:显然这种方法是本文重点,不过方法1也会一并用起。
以一个很常见的图像彩色转灰度为例。
根据色彩学上的一些理论,将一个RGB彩色像素转换成灰度,实际上是一个1*3矩阵和一个3*1矩阵相乘,说白了就是如下过程:
设原像素为p0 = (r0,g0,b0),转换为s=(r0*0.3,g0*0.6,b0*0.1),然后新的灰度像素p1=(s,s,s)。
这里可以看到,求得s值这一步中,有三次浮点运算,我们可以用方法1将这里的浮点运算暂时化为整数(全部乘以10),即
s=(r0*3,g0*6,b0*1),最后一次性除以10。
具体代码如下:
void doProcess(PBYTE pIn, DWORD size, DWORD width, DWORD height, DWORD bitCount) 

  DWORD dwRGBSum = 0; 
  for(DWORD dwIndex = 0; dwIndex < size; dwIndex+=3) 
  { 
    dwRGBSum =    
      1 * pIn[dwIndex+0] +     //Blue  
      6 * pIn[dwIndex+1] +     //Green  
      3 * pIn[dwIndex+2];        //Red  
    dwRGBSum /= 10.0; 
    pIn[dwIndex+0] = dwRGBSum; 
    pIn[dwIndex+1] = dwRGBSum; 
    pIn[dwIndex+2] = dwRGBSum; 
  } 

现在我们再来使用SSE来进一步优化。
SSE一次性可以处理128位的运算,即4个浮点数。因而我们将四次除法放在一次进行,核心的一个数据结构是__m128,这是一个联合体,具体参见其源码。

SSE中批量浮点数乘法对应的C函数是_mm_mul_ps。用法可以参考MSDN或者INTEL官方网站上的一个PDF。

void doProcess(PBYTE pIn, DWORD size, DWORD width, DWORD height, DWORD bitCount) 

  UINT16 dwRGBSum0 = 0; 
  UINT16 dwRGBSum1 = 0; 
  UINT16 dwRGBSum2 = 0; 
  UINT16 dwRGBSum3 = 0; 

  for(DWORD idx = 0; idx < size; idx+=12) 
  { 
    dwRGBSum0 =    
      1 * pIn[idx+0] +     //Blue 
      6 * pIn[idx+1] +     //Green 
      3 * pIn[idx+2];        //Red 

    dwRGBSum1 =    
      1 * pIn[idx+3] +     //Blue 
      6 * pIn[idx+4] +     //Green 
      3 * pIn[idx+5];        //Red 

    dwRGBSum2 =    
      1 * pIn[idx+6] +     //Blue 
      6 * pIn[idx+7] +     //Green 
      3 * pIn[idx+8];        //Red 

    dwRGBSum3 =    
      1 * pIn[idx+9] +     //Blue 
      6 * pIn[idx+10] +     //Green 
      3 * pIn[idx+11];        //Red 


    __m128 old = _mm_set_ps(dwRGBSum0, dwRGBSum1, dwRGBSum2, dwRGBSum3); 
    __m128 ret = _mm_mul_ps(old, vec); 

    pIn[idx+0] = pIn[idx+1] = pIn[idx+2] = (BYTE)ret.m128_f32[3]; 
    pIn[idx+3] = pIn[idx+4] = pIn[idx+5] = (BYTE)ret.m128_f32[2]; 
    pIn[idx+6] = pIn[idx+7] = pIn[idx+8] = (BYTE)ret.m128_f32[1]; 
    pIn[idx+9] = pIn[idx+10] = pIn[idx+11] = (BYTE)ret.m128_f32[0]; 
  } 
}
代码看上去要比原来的复杂许多,不过其实原理很简单的,原来一次性处理一个像素,现在一次性处理4个,性能和效率大大提升了。

这个代码其实还可以优化的,因为SSE内除了浮点可以批量处理,整数也是可以的,对应的数据结构是__m128i。涉及到一些矩阵的知识,我就不多说了。

上面这个优化性能测试结果还是很明显的,原来的程序对一个2560*1600,24位色深的图片进行转换,需要将近800ms的时间,优化后,只需450ms了,提高了将近一倍。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSE 指令集本身并没有提供直接的 16 进制转换函数,但可以使用 SSE 指令集提供的逻辑运算指令和移位指令,通过位操作实现 16 进制转换。下面是一个使用 SSE 指令集实现 16 进制转换的例子: ```c++ #include <iostream> #include <emmintrin.h> // SSE2 int main() { int arr[4] = {0x12345678, 0x9ABCDEF0, 0xABCDEF01, 0x23456789}; __m128i vec = _mm_loadu_si128((__m128i*)arr); // 将 arr 中的 4 个 int 数据加载到 SSE 寄存器 vec 中 __m128i mask = _mm_set_epi32(0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F, 0x0F0F0F0F); // 创建一个包含 4 个 0x0F 的 SSE 寄存器 mask __m128i low = _mm_and_si128(vec, mask); // 将 vec 中的数据与 mask 中的数据进行按位与操作,获取低 4 位数据 __m128i high = _mm_srli_epi32(vec, 4); // 将 vec 中的数据进行右移 4 位操作,获取高 4 位数据 __m128i high_mask = _mm_set_epi32(0x0F0F0F00, 0x0F0F0F00, 0x0F0F0F00, 0x0F0F0F00); // 创建一个包含 4 个 0x0F0F0F00 的 SSE 寄存器 high_mask high = _mm_and_si128(high, high_mask); // 将 high 中的数据与 high_mask 中的数据进行按位与操作,获取高 4 位数据 __m128i result = _mm_or_si128(high, low); // 将 high 和 low 中的数据进行按位或操作,获取最终结果 unsigned int result_arr[4]; _mm_storeu_si128((__m128i*)result_arr, result); // 将 result 中的数据存储到 result_arr 数组中 for (int i = 0; i < 4; i++) { printf("%08X\n", result_arr[i]); } return 0; } ``` 在这个例子中,我们首先定义了一个包含 4 个 int 类型数据的数组 arr,然后使用 _mm_loadu_si128 函数将 arr 中的数据加载到 SSE 寄存器 vec 中。接着使用 _mm_set_epi32 函数创建了一个包含 4 个 0x0F 的 SSE 寄存器 mask,用于获取低 4 位数据。然后使用 _mm_and_si128 函数将 vec 中的数据与 mask 中的数据进行按位与操作,获取低 4 位数据。接着使用 _mm_srli_epi32 函数将 vec 中的数据进行右移 4 位操作,获取高 4 位数据。然后使用 _mm_set_epi32 函数创建了一个包含 4 个 0x0F0F0F00 的 SSE 寄存器 high_mask,用于获取高 4 位数据。接着使用 _mm_and_si128 函数将 high 中的数据与 high_mask 中的数据进行按位与操作,获取高 4 位数据。最后,使用 _mm_or_si128 函数将 high 和 low 中的数据进行按位或操作,获取最终结果。遍历结果数组,使用 printf 函数将结果以 16 进制的形式输出。 需要注意的是,在使用 SSE 指令集进行 16 进制转换时,需要注意数据对齐的问题,否则可能会导致程序崩溃或数据错误。此外,SSE 指令集提供的计算精度较高,可以提高计算精度和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值