SSE指令介绍及其C、C++应用

SSE指令介绍及其C、C++应用 收藏

<script type="text/javascript"> document.body.oncopy = function() { if (window.clipboardData) { setTimeout(function() { var text = clipboardData.getData("text"); if (text && text.length>300) { text = text + "/r/n/n本文来自CSDN博客,转载请标明出处:" + location.href; clipboardData.setData("text", text); } }, 100); } } </script> <script class="blogstory">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>

http://blog.csdn.net/olncy/archive/2009/04/16/4084374.aspx

作者:Alex Farber
出处:http://www.codeproject.com/cpp/sseintro.asp

SSE技术简介

Intel公司的单指令多数据流式扩展(SSE,Streaming SIMD Extensions)技术能够有效增强CPU浮点运算的能力。Visual Studio 提供了对SSE指令集的编程支持,从而允许用户在C++代码中不用编写汇编代码就可直接使用SSE指令的功能。MSDN中有关SSE技术的主题 [1]有可能会使不熟悉使用SSE汇编指令编程的初学者感到困惑,但是在阅读MSDN有关文档的同时,参考一下Intel软件说明书(Intel Software manuals)[2]会使你更清楚地理解使用SSE指令编程的要点。

SIMD(single-instruction, multiple-data)是一种使用单道指令处理多道数据流的CPU执行模式,即在一个CPU指令执行周期内用一道指令完成处理多个数据的操作。考虑 一下下面这个任务:计算一个很长的浮点型数组中每一个元素的平方根。实现这个任务的算法可以这样写:

  1. for  each   f in array       //对数组中的每一个元素   
  2.      f = sqrt(f)              //计算它的平方根   
  3. end for   

为了了解实现的细节,我们把上面的代码这样写:

  1. for  each   f in array  
  2. {  
  3.      把f从内存加载到浮点寄存器  
  4.      计算平方根  
  5.      再把计算结果从寄存器中取出放入内存  
  6. }  

具有Intel SSE指令集支持的处理器有8个128位的寄存器,每一个寄存器可以存放4个(32位)单精度的浮点数。SSE同时提供了一个指令集,其中的指令可以允许 把浮点数加载到这些128位的寄存器之中,这些数就可以在这些寄存器中进行算术逻辑运算,然后把结果放回内存。采用SSE技术后,算法可以写成下面的样 子:

  1. for  each   4 members in array    //对数组中的每4个元素   
  2. {  
  3.      把数组中的这4个数加载到一个128位的SSE寄存器中  
  4.      在一个CPU指令执行周期中完成计算这4个数的平方根的操作  
  5.      把所得的4个结果取出写入内存  
  6. }  

C++编程人员在使用SSE指令函数编程时不必关心这些128位的寄存器,你可以使用128位的数据类型“__m128”和一系列C++函数来实现 这些算术和逻辑操作,而决定程序使用哪个SSE寄存器以及代码优化是C++编译器的任务。当需要对很长的浮点数数组中的元素进行处理的时候,SSE技术确 实是一种很高效的方法。

SSE程序设计详细介绍

包含的头文件:

所有的SSE指令函数和__m128数据类型都在xmmintrin.h文件中定义:

  1. #include <xmmintrin.h>   


因为程序中用到的SSE处理器指令是由编译器决定,所以它并没有相关的.lib库文件。

数据分组(Data Alignment)

由SSE指令处理的每一个浮点数数组必须把其中需要处理的数每16个字节(128位二进制)分为一组。一个静态数组(static array)可由__declspec(align(16))关键字声明:

  1. __declspec (align(16))  float  m_fArray[ARRAY_SIZE];  

动态数组(dynamic array)可由_aligned_malloc函数为其分配空间:

  1. m_fArray = ( float *) _aligned_malloc(ARRAY_SIZE *  sizeof ( float ), 16);  

由_aligned_malloc函数分配空间的动态数组可以由_aligned_free函数释放其占用的空间:

  1. _aligned_free(m_fArray);  

__m128 数据类型

该数据类型的变量可用做SSE指令的操作数,它们不能被用户指令直接存取。_m128类型的变量被自动分配为16个字节的字长。

CPU对SSE指令集的支持

如果你的CPU能够具有了SSE指令集,你就可以使用Visual Studio .NET 提供的对SSE指令集支持的C++函数库了,你可以查看MSDN中的一个Visual C++ CPUID的例子[4],它可以帮你检测你的CPU是否支持SSE、MMX指令集或其它的CPU功能。


编程实例
以下讲解了SSE技术在Visual Studio .NET 2003下的应用实例,你可以在http://www.codeproject.com/cpp/sseintro/SSE_src.zip 下载示例程序压缩包。该压缩包中含有两个项目,这两个项目是基于微软基本类库(MFC)建立的Visual C++.NET项目,你也可以按照下面的讲解建立这两个项目。

SSETest 示例项目

SSETest项目是一个基于对话框的应用程序,它用到了三个浮点数组参与运算:

  1. fResult[i] = sqrt( fSource1[i]*fSource1[i] + fSource2[i]*fSource2[i] ) + 0.5  

其中i = 0, 1, 2 ... ARRAY_SIZE-1

其中ARRAY_SIZE被定义为30000。数据源数组(Source数组)通过使用sin和cos函数给它赋值,我们用Kris Jearakul开发的瀑布状图表控件(Waterfall chart control)[3] 来显示参与计算的源数组和结果数组。计算所需的时间(以毫秒ms为单位)在对话框中显示出来。我们使用三种不同的途径来完成计算:

纯C++代码;
使用SSE指令函数的C++代码;
包含SSE汇编指令的代码。


纯C++代码:

  1. void  CSSETestDlg::ComputeArrayCPlusPlus(  
  2.            float * pArray1,                     // [输入] 源数组1   
  3.            float * pArray2,                     // [输入] 源数组2   
  4.            float * pResult,                     // [输出] 用来存放结果的数组   
  5.            int  nSize)                              // [输入] 数组的大小   
  6. {  
  7.      int  i;  
  8.      float * pSource1 = pArray1;  
  9.      float * pSource2 = pArray2;  
  10.      float * pDest = pResult;  
  11.      for  ( i = 0; i < nSize; i++ )  
  12.      {  
  13.          *pDest = (float )sqrt((*pSource1) * (*pSource1) + (*pSource2)  
  14.                   * (*pSource2)) + 0.5f;  
  15.          pSource1++;  
  16.          pSource2++;  
  17.          pDest++;  
  18.      }  
  19. }  


下面我们用具有SSE特性的C++代码重写上面这个函数。为了查询使用SSE指令C++函数的方法,我参考了Intel软件说明书 (Intel Software manuals)中有关SSE汇编指令的说明,首先我是在第一卷的第九章找到的相关SSE指令,然后在第二卷找到了这些SSE指令的详细说明,这些说明有 一部分涉及了与其特性相关的C++函数。然后我通过这些SSE指令对应的C++函数查找了MSDN中与其相关的说明。搜索的结果见下表:

实现的功能 对应的SSE汇编指令 Visual C++.NET中的SSE函数
将4个32位浮点数放进一个128位的存储单元。 movss 和 shufps _mm_set_ps1 
将4对32位浮点数同时进行相乘操作。这4对32位浮点数来自两个128位的存储单元,再把计算结果(乘积)赋给一个128位的存储单元。 mulps _mm_mul_ps
将4对32位浮点数同时进行相加操作。这4对32位浮点数来自两个128位的存储单元,再把计算结果(相加之和)赋给一个128位的存储单元。 addps _mm_add_ps
对一个128位存储单元中的4个32位浮点数同时进行求平方根操作。 sqrtps _mm_sqrt_ps

使用Visual C++.NET的 SSE指令函数的代码:

  1. void  CSSETestDlg::ComputeArrayCPlusPlusSSE(  
  2.            float * pArray1,                     // [输入] 源数组1   
  3.            float * pArray2,                     // [输入] 源数组2   
  4.            float * pResult,                     // [输出] 用来存放结果的数组   
  5.            int  nSize)                          // [输入] 数组的大小   
  6. {  
  7.      int  nLoop = nSize/ 4;  
  8.      __m128 m1, m2, m3, m4;  
  9.      __m128* pSrc1 = (__m128*) pArray1;  
  10.      __m128* pSrc2 = (__m128*) pArray2;  
  11.      __m128* pDest = (__m128*) pResult;  
  12.   
  13.      __m128 m0_5 = _mm_set_ps1(0.5f);         // m0_5[0, 1, 2, 3] = 0.5   
  14.      for  (  int  i = 0; i < nLoop; i++ )  
  15.      {  
  16.          m1 = _mm_mul_ps(*pSrc1, *pSrc1);         // m1 = *pSrc1 * *pSrc1   
  17.          m2 = _mm_mul_ps(*pSrc2, *pSrc2);         // m2 = *pSrc2 * *pSrc2   
  18.          m3 = _mm_add_ps(m1, m2);                 // m3 = m1 + m2   
  19.          m4 = _mm_sqrt_ps(m3);                    // m4 = sqrt(m3)   
  20.          *pDest = _mm_add_ps(m4, m0_5);           // *pDest = m4 + 0.5   
  21.           
  22.          pSrc1++;  
  23.          pSrc2++;  
  24.          pDest++;  
  25.      }  
  26. }  

使用SSE汇编指令实现的C++函数代码:

  1. void  CSSETestDlg::ComputeArrayAssemblySSE(  
  2.            float * pArray1,                     // [输入] 源数组1   
  3.            float * pArray2,                     // [输入] 源数组2   
  4.            float * pResult,                     // [输出] 用来存放结果的数组   
  5.            int  nSize)                          // [输入] 数组的大小   
  6. {  
  7.      int  nLoop = nSize/4;  
  8.      float  f = 0.5f;  
  9.      _asm  
  10.      {  
  11.          movss    xmm2, f                          // xmm2[0] = 0.5   
  12.          shufps   xmm2, xmm2, 0                    // xmm2[1, 2, 3] = xmm2[0]   
  13.          mov          esi, pArray1                 // 输入的源数组1的地址送往esi   
  14.          mov          edx, pArray2                 // 输入的源数组2的地址送往edx   
  15.          mov          edi, pResult                 // 输出结果数组的地址保存在edi   
  16.          mov          ecx, nLoop                   //循环次数送往ecx   
  17. start_loop:  
  18.          movaps       xmm0, [esi]                  // xmm0 = [esi]   
  19.          mulps        xmm0, xmm0                   // xmm0 = xmm0 * xmm0   
  20.          movaps       xmm1, [edx]                  // xmm1 = [edx]   
  21.          mulps        xmm1, xmm1                   // xmm1 = xmm1 * xmm1   
  22.          addps        xmm0, xmm1                   // xmm0 = xmm0 + xmm1   
  23.          sqrtps       xmm0, xmm0                   // xmm0 = sqrt(xmm0)   
  24.          addps        xmm0, xmm2                   // xmm0 = xmm1 + xmm2   
  25.          movaps       [edi], xmm0                  // [edi] = xmm0   
  26.          add          esi, 16                      // esi += 16   
  27.          add          edx, 16                      // edx += 16   
  28.          add          edi, 16                      // edi += 16   
  29.          dec          ecx                          // ecx--   
  30.          jnz          start_loop                 //如果不为0则转向start_loop   
  31.      }  
  32. }  

最后,在我的计算机上运行计算测试的结果:

纯C++代码计算所用的时间是26 毫秒
使用SSE的C++ 函数计算所用的时间是 9 毫秒
包含SSE汇编指令的C++代码计算所用的时间是 9 毫秒

以上的时间结果是在Release优化编译后执行程序得出的。 

SSESample 示例项目

SSESample项目是一个基于对话框的应用程序,其中它用下面的浮点数数组进行计算:

fResult[i] = sqrt(fSource[i]*2.8)

其中i = 0, 1, 2 ... ARRAY_SIZE-1

这个程序同时计算了数组中的最大值和最小值。ARRAY_SIZE被定义为100000,数组中的计算结果在列表框中显示出来。其中在我的机子上用下面三种方法计算所需的时间是:
纯C++代码计算                    6 毫秒
使用SSE的C++ 函数计算      3 毫秒
使用SSE汇编指令计算          2 毫秒

大家看到,使用SSE汇编指令计算的结果会好一些,因为使用了效率增强了的SSX寄存器组。但是在通常情况下,使用SSE的C++ 函数计算会比汇编代码计算的效率更高一些,因为C++编译器的优化后的代码有很高的运算效率,若要使汇编代码比优化后的代码运算效率更高,这通常是很难做 到的。

纯C++代码:

  1. // 输入: m_fInitialArray   
  2. // 输出: m_fResultArray, m_fMin, m_fMax   
  3. void  CSSESampleDlg::OnBnClickedButtonCplusplus()  
  4. {  
  5.      m_fMin = FLT_MAX;  
  6.      m_fMax = FLT_MIN;  
  7.      int  i;  
  8.      for  ( i = 0; i < ARRAY_SIZE; i++ )  
  9.      {  
  10.          m_fResultArray[i] = sqrt(m_fInitialArray[i]   * 2.8f);  
  11.          if  ( m_fResultArray[i] < m_fMin )  
  12.              m_fMin = m_fResultArray[i];  
  13.          if  ( m_fResultArray[i] > m_fMax )  
  14.              m_fMax = m_fResultArray[i];  
  15.      }  
  16. }  

使用Visual C++.NET的 SSE指令函数的代码:

  1. // 输入: m_fInitialArray   
  2. // 输出: m_fResultArray, m_fMin, m_fMax   
  3. void  CSSESampleDlg::OnBnClickedButtonSseC()  
  4. {  
  5.      __m128 coeff = _mm_set_ps1(2.8f);       // coeff[0, 1, 2, 3] = 2.8   
  6.      __m128 tmp;  
  7.      __m128 min128 = _mm_set_ps1(FLT_MAX);   // min128[0, 1, 2, 3] = FLT_MAX   
  8.      __m128 max128 = _mm_set_ps1(FLT_MIN);   // max128[0, 1, 2, 3] = FLT_MIN   
  9.      __m128* pSource = (__m128*) m_fInitialArray;  
  10.      __m128* pDest = (__m128*) m_fResultArray;  
  11.      for  (  int  i = 0; i < ARRAY_SIZE/4; i++ )  
  12.      {  
  13.          tmp = _mm_mul_ps(*pSource, coeff);       // tmp = *pSource * coeff   
  14.          *pDest = _mm_sqrt_ps(tmp);               // *pDest = sqrt(tmp)   
  15.          min128 =   _mm_min_ps(*pDest, min128);  
  16.          max128 =   _mm_max_ps(*pDest, max128);  
  17.          pSource++;  
  18.          pDest++;  
  19.      }  
  20.      // 计算max128的最大值和min128的最小值   
  21.      union  u  
  22.      {  
  23.          __m128 m;  
  24.          float  f[4];  
  25.      } x;  
  26.      x.m = min128;  
  27.      m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));  
  28.      x.m = max128;  
  29.      m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));  
  30. }  

使用SSE汇编指令的C++函数代码:

  1. // 输入: m_fInitialArray   
  2. // 输出: m_fResultArray, m_fMin, m_fMax   
  3. void  CSSESampleDlg::OnBnClickedButtonSseAssembly()  
  4. {  
  5.      
  6.      float * pIn = m_fInitialArray;  
  7.      float * pOut = m_fResultArray;  
  8.      float  f = 2.8f;  
  9.      float  flt_min = FLT_MIN;  
  10.      float  flt_max = FLT_MAX;  
  11.      __m128 min128;  
  12.      __m128 max128;  
  13.      // 使用以下的附加寄存器:xmm2、xmm3、xmm4:   
  14.      // xmm2 – 相乘系数   
  15.      // xmm3 – 最小值   
  16.      // xmm4 – 最大值   
  17.      _asm  
  18.      {  
  19.          movss    xmm2, f                          // xmm2[0] = 2.8   
  20.          shufps   xmm2, xmm2, 0                    // xmm2[1, 2, 3] = xmm2[0]   
  21.          movss    xmm3, flt_max                    // xmm3 = FLT_MAX   
  22.          shufps   xmm3, xmm3, 0                    // xmm3[1, 2, 3] = xmm3[0]   
  23.          movss    xmm4, flt_min                    // xmm4 = FLT_MIN   
  24.          shufps   xmm4, xmm4, 0                    // xmm3[1, 2, 3] = xmm3[0]   
  25.          mov          esi, pIn                     // 输入数组的地址送往esi   
  26.          mov          edi, pOut                    // 输出数组的地址送往edi   
  27.          mov          ecx, ARRAY_SIZE/4            // 循环计数器初始化   
  28. start_loop:  
  29.          movaps       xmm1, [esi]                  // xmm1 = [esi]   
  30.          mulps        xmm1, xmm2                   // xmm1 = xmm1 * xmm2   
  31.          sqrtps       xmm1, xmm1                   // xmm1 = sqrt(xmm1)   
  32.          movaps       [edi], xmm1                  // [edi] = xmm1   
  33.          minps        xmm3, xmm1  
  34.          maxps        xmm4, xmm1  
  35.          add          esi, 16  
  36.          add          edi, 16  
  37.          dec          ecx  
  38.          jnz          start_loop  
  39.   
  40.          movaps       min128, xmm3  
  41.          movaps       max128, xmm4  
  42.      }  
  43.      union  u  
  44.      {  
  45.          __m128 m;  
  46.          float  f[4];  
  47.      } x;  
  48.      x.m = min128;  
  49.      m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));  
  50.      x.m = max128;  
  51.      m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));  
  52. }  


参考文档:

[1]MSDN, SSE技术主题:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/vcrefstreamingsimdextensions.asp

[2]Intel软件说明书(Intel Software manuals):
http://developer.intel.com/design/archives/processors/mmx/index.htm

[3] Kris Jearakul的瀑布状图表控件:http://www.codeguru.com/controls/Waterfall.shtml

[4] Microsoft Visual C++ CPUID示例:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/vcsamcpuiddeterminecpucapabilities.asp

[5] Matt Pietrek在Microsoft Systems Journal 1998年2月刊上的评论文章:
http://www.microsoft.com/msj/0298/hood0298.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSE(Streaming SIMD Extensions)是一组针对向量数据的指令集,它可以加速一些数学计算、图形处理和多媒体应用。在C++中,可以使用SSE指令集来实现高效的向量运算。 首先,需要确保编译器支持SSE指令集。大多数现代的编译器都支持SSE指令集,例如gcc和Visual C++。 下面是一个简单的示例,演示如何使用SSE指令集来计算两个向量的点积: ```c++ #include <emmintrin.h> // SSE2指令集需要的头文件 float dot_product(const float* a, const float* b, int n) { __m128 sum = _mm_setzero_ps(); // 初始化累加器 for (int i = 0; i < n; i += 4) { __m128 v1 = _mm_loadu_ps(a + i); // 从内存中加载4个单精度浮点数 __m128 v2 = _mm_loadu_ps(b + i); sum = _mm_add_ps(sum, _mm_mul_ps(v1, v2)); // 累加向量乘积 } float result[4]; _mm_storeu_ps(result, sum); // 将累加器的值存储到result中 return result[0] + result[1] + result[2] + result[3]; // 对结果进行累加 } ``` 在上面的代码中,我们使用了SSE2指令集中的__m128数据类型,它表示一个包含4个单精度浮点数的向量。_mm_setzero_ps函数用于初始化累加器为全0向量,_mm_loadu_ps函数用于从内存中加载4个单精度浮点数,_mm_add_ps函数用于向量加法,_mm_mul_ps函数用于向量乘法,_mm_storeu_ps函数用于将向量的值存储到内存中。 需要注意的是,SSE指令集中的数据类型和函数都以_mm开头,而且需要使用特殊的头文件<emmintrin.h>来包含SSE指令集所需的函数和类型定义。另外,SSE指令集要求内存对齐,因此需要使用_mm_loadu_ps和_mm_storeu_ps等函数来处理非对齐内存。 总之,SSE指令集可以帮助我们实现高效的向量计算,但需要注意数据类型和内存对齐等细节。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值