现在的位置: 首页 > 综合 > 正文

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

2013年10月03日 ⁄ 综合 ⁄ 共 4883字 ⁄ 字号 评论关闭

转自 http://blog.csdn.net/hikaliv/article/details/4264188

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.      __m128 m0_5 = _mm_set_ps1(0.5f);         // m0_5[0, 1, 2, 3] = 0.5  
  13.      for ( int i = 0; i < nLoop; i++ )  
  14.      {  
  15.          m1 = _mm_mul_ps(*pSrc1, *pSrc1);         // m1 = *pSrc1 * *pSrc1  
  16.          m2 = _mm_mul_ps(*pSrc2, *pSrc2);         // m2 = *pSrc2 * *pSrc2  
  17.          m3 = _mm_add_ps(m1, m2);                 // m3 = m1 + m2  
  18.          m4 = _mm_sqrt_ps(m3);                    // m4 = sqrt(m3)  
  19.          *pDest = _mm_add_ps(m4, m0_5);           // *pDest = m4 + 0.5  
  20.           
  21.          pSrc1++;  
  22.          pSrc2++;  
  23.          pDest++;  
  24.      }  
  25. }  

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

抱歉!评论已关闭.