基于SSE指令集的程序设计简介

 

 

 

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

 

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

 

for each f in array //对数组中的每一个元素

f = sqrt(f) //计算它的平方根

 

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

 

for each f in array

{

f从内存加载到浮点寄存器

计算平方根

再把计算结果从寄存器中取出放入内存

}

 

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

 

for each 4 members in array //对数组中的每4个元素

{

把数组中的这4个数加载到一个128位的SSE寄存器中

在一个CPU指令执行周期中完成计算这4个数的平方根的操作

把所得的4个结果取出写入内存

}

 

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

 

 

SSE程序设计详细介绍

 

包含的头文件:

 

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

#include

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

 

数据分组(DataAlignment

 

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

 

__declspec(align(16)) floatm_fArray[ARRAY_SIZE];

 

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

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

 

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

_aligned_free(m_fArray);

 

__m128 数据类型

 

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

 

CPUSSE指令集的支持

 

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

 

 

编程实例

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

 

SSETest 示例项目

 

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

 

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

 

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

 

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

 

C++代码;

使用SSE指令函数的C++代码;

包含SSE汇编指令的代码。

 

 

C++代码:

 

void CSSETestDlg::ComputeArrayCPlusPlus(

float* pArray1, // [输入]源数组1

float* pArray2, // [输入]源数组2

float* pResult, // [输出]用来存放结果的数组

int nSize) // [输入]数组的大小

{

 

int i;

 

float* pSource1 = pArray1;

float* pSource2 = pArray2;

float* pDest = pResult;

 

for ( i = 0; i < nSize; i++ )

{

*pDest =(float)sqrt((*pSource1) * (*pSource1) + (*pSource2)

* (*pSource2)) +0.5f;

 

pSource1++;

pSource2++;

pDest++;

}

}

 

 

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

 

实现的功能对应的SSE汇编指令 VisualC++.NET中的SSE函数

432位浮点数放进一个128位的存储单元。 movss shufps _mm_set_ps1

432位浮点数同时进行相乘操作。这432位浮点数来自两个128位的存储单元,再把计算结果(乘积)赋给一个128位的存储单元。 mulps _mm_mul_ps

432位浮点数同时进行相加操作。这432位浮点数来自两个128位的存储单元,再把计算结果(相加之和)赋给一个128位的存储单元。 addps _mm_add_ps

 

本篇文章来源于黑软基地-中国最大的黑客软件安全教程下载站!(技术学院)原文链接:http://www.hackvip.com/article/sort0129/sort0143/Hackvip_235395.html

 

对一个128位存储单元中的432位浮点数同时进行求平方根操作。 sqrtps _mm_sqrt_ps




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

void CSSETestDlg::ComputeArrayCPlusPlusSSE(
float* pArray1, // [
输入]源数组1
float* pArray2, // [
输入]源数组2
float* pResult, // [
输出]用来存放结果的数组
int nSize) // [
输入]数组的大小
{
int nLoop = nSize/ 4;

__m128 m1, m2, m3, m4;

__m128* pSrc1 = (__m128*) pArray1;
__m128* pSrc2 = (__m128*) pArray2;
__m128* pDest = (__m128*) pResult;


__m128 m0_5 = _mm_set_ps1(0.5f); // m0_5[0, 1, 2, 3] = 0.5

for ( int i = 0; i < nLoop; i++ )
{
m1 = _mm_mul_ps(*pSrc1, *pSrc1); // m1 = *pSrc1 * *pSrc1
m2 = _mm_mul_ps(*pSrc2, *pSrc2); // m2 = *pSrc2 * *pSrc2
m3 = _mm_add_ps(m1, m2); // m3 = m1 + m2
m4 = _mm_sqrt_ps(m3); // m4 = sqrt(m3)
*pDest = _mm_add_ps(m4, m0_5); // *pDest = m4 + 0.5

pSrc1++;
pSrc2++;
pDest++;
}
}

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

void CSSETestDlg::ComputeArrayAssemblySSE(
float* pArray1, // [
输入]源数组1
float* pArray2, // [
输入]源数组2
float* pResult, // [
输出]用来存放结果的数组
int nSize) // [
输入]数组的大小
{
int nLoop = nSize/4;
float f = 0.5f;

_asm
{
movss xmm2, f // xmm2[0] = 0.5
shufps xmm2, xmm2, 0 // xmm2[1, 2, 3] = xmm2[0]

mov esi, pArray1 //
输入的源数组1的地址送往esi
mov edx, pArray2 //
输入的源数组2的地址送往edx

mov edi, pResult //
输出结果数组的地址保存在edi
mov ecx, nLoop //
循环次数送往ecx

start_loop:
movaps xmm0, [esi] // xmm0 = [esi]
mulps xmm0, xmm0 // xmm0 = xmm0 * xmm0

movaps xmm1, [edx] // xmm1 = [edx]
mulps xmm1, xmm1 // xmm1 = xmm1 * xmm1

addps xmm0, xmm1 // xmm0 = xmm0 + xmm1
sqrtps xmm0, xmm0 // xmm0 = sqrt(xmm0)

addps xmm0, xmm2 // xmm0 = xmm1 + xmm2

movaps [edi], xmm0 // [edi] = xmm0

add esi, 16 // esi += 16
add edx, 16 // edx += 16
add edi, 16 // edi += 16

dec ecx // ecx--
jnz start_loop //
如果不为0则转向start_loop
}
}

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

C++代码计算所用的时间是26毫秒
使用SSEC++函数计算所用的时间是 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毫秒
使用SSEC++函数计算 3 毫秒
使用SSE汇编指令计算 2毫秒

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

C++代码:

//
输入:m_fInitialArray
//
输出:m_fResultArray, m_fMin, m_fMax
void CSSESampleDlg::OnBnClickedButtonCplusplus()
{
m_fMin = FLT_MAX;
m_fMax = FLT_MIN;

int i;

for ( i = 0; i < ARRAY_SIZE; i++ )
{
m_fResultArray[i] = sqrt(m_fInitialArray[i] * 2.8f);

if ( m_fResultArray[i] < m_fMin )
m_fMin = m_fResultArray[i];

if ( m_fResultArray[i] > m_fMax )
m_fMax = m_fResultArray[i];
}
}



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


//
输入:m_fInitialArray
//
输出:m_fResultArray, m_fMin, m_fMax
void CSSESampleDlg::OnBnClickedButtonSseC()
{
__m128 coeff = _mm_set_ps1(2.8f); // coeff[0, 1, 2, 3] = 2.8
__m128 tmp;

__m128 min128 = _mm_set_ps1(FLT_MAX); // min128[0, 1, 2, 3] = FLT_MAX
__m128 max128 = _mm_set_ps1(FLT_MIN); // max128[0, 1, 2, 3] = FLT_MIN

__m128* pSource = (__m128*) m_fInitialArray;
__m128* pDest = (__m128*) m_fResultArray;

for ( int i = 0; i < ARRAY_SIZE/4; i++ )
{
tmp = _mm_mul_ps(*pSource, coeff); // tmp = *pSource * coeff
*pDest = _mm_sqrt_ps(tmp); // *pDest = sqrt(tmp)

min128 = _mm_min_ps(*pDest, min128);
max128 = _mm_max_ps(*pDest, max128);

pSource++;
pDest++;
}

//
计算max128的最大值和min128的最小值
union u
{
__m128 m;
float f[4];
} x;

x.m = min128;
m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));

x.m = max128;
m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));
}



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


//
输入:m_fInitialArray
//
输出:m_fResultArray, m_fMin, m_fMax
void CSSESampleDlg::OnBnClickedButtonSseAssembly()
{

float* pIn = m_fInitialArray;
float* pOut = m_fResultArray;

float f = 2.8f;
float flt_min = FLT_MIN;
float flt_max = FLT_MAX;

 


__m128 min128;
__m128 max128;

//
使用以下的附加寄存器:xmm2xmm3xmm4:
// xmm2 –
相乘系数
// xmm3 –
最小值
// xmm4 –
最大值

_asm
{
movss xmm2, f // xmm2[0] = 2.8
shufps xmm2, xmm2, 0 // xmm2[1, 2, 3] = xmm2[0]

movss xmm3, flt_max // xmm3 = FLT_MAX
shufps xmm3, xmm3, 0 // xmm3[1, 2, 3] = xmm3[0]

movss xmm4, flt_min // xmm4 = FLT_MIN
shufps xmm4, xmm4, 0 // xmm3[1, 2, 3] = xmm3[0]

mov esi, pIn //
输入数组的地址送往esi
mov edi, pOut //
输出数组的地址送往edi
mov ecx, ARRAY_SIZE/4 //
循环计数器初始化

start_loop:
movaps xmm1, [esi] // xmm1 = [esi]
mulps xmm1, xmm2 // xmm1 = xmm1 * xmm2
sqrtps xmm1, xmm1 // xmm1 = sqrt(xmm1)
movaps [edi], xmm1 // [edi] = xmm1

minps xmm3, xmm1
maxps xmm4, xmm1

add esi, 16
add edi, 16

dec ecx
jnz start_loop


movaps min128, xmm3
movaps max128, xmm4
}

union u
{
__m128 m;
float f[4];
} x;

x.m = min128;
m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));

x.m = max128;
m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));

}

SSE技术简介

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

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

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

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

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

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

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

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


SSE程序设计详细介绍

包含的头文件:

所有的SSE指令函数和__m128数据类型都在xmmintrin.h文件中定义:
#include <xmmintrin.h>
因为程序中用到的SSE处理器指令是由编译器决定,所以它并没有相关的.lib库文件。

数据分组(Data Alignment)

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

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

动态数组(dynamic array)可由_aligned_malloc函数为其分配空间:
m_fArray = (float*) _aligned_malloc(ARRAY_SIZE * sizeof(float), 16);

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

__m128 数据类型

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

CPU对SSE指令集的支持

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


编程实例

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

SSETest 示例项目

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

fResult = sqrt( fSource1*fSource1 + fSource2*fSource2 ) + 0.5

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

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

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

纯C++代码:

1.  void CSSETestDlg::ComputeArrayCPlusPlus(

2.           float* pArray1,                  // [输入]源数组1

3.           float* pArray2,                  // [输入]源数组2

4.           float* pResult,                  // [输出]用来存放结果的数组

5.            intnSize)                           // [输入]数组的大小

6.  {

7.   

8.      int i;

9.   

10.     float* pSource1 = pArray1;

11.     float* pSource2 = pArray2;

12.     float* pDest = pResult;

13.  

14.     for ( i = 0; i < nSize;i++ )

15.     {

16.        *pDest = (float)sqrt((*pSource1) * (*pSource1) + (*pSource2)

17.                 * (*pSource2)) + 0.5f;

18.  

19.        pSource1++;

20.        pSource2++;

21.        pDest++;

22.     }

23. }

复制代码

下面我们用具有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.            intnSize)                       // [输入]数组的大小

6.  {

7.      int nLoop = nSize/ 4;

8.   

9.      __m128 m1, m2, m3, m4;

10.  

11.     __m128* pSrc1 = (__m128*)pArray1;

12.     __m128* pSrc2 = (__m128*)pArray2;

13.     __m128* pDest = (__m128*)pResult;

14.  

15.  

16.     __m128 m0_5 =_mm_set_ps1(0.5f);        // m0_5[0, 1, 2, 3] =0.5

17.  

18.     for ( int i = 0; i <nLoop; i++ )

19.     {

20.         m1 =_mm_mul_ps(*pSrc1, *pSrc1);        // m1 = *pSrc1* *pSrc1

21.         m2 =_mm_mul_ps(*pSrc2, *pSrc2);        // m2 = *pSrc2* *pSrc2

22.         m3 =_mm_add_ps(m1, m2);               // m3 = m1 + m2

23.         m4 =_mm_sqrt_ps(m3);                  // m4 = sqrt(m3)

24.        *pDest = _mm_add_ps(m4, m0_5);         // *pDest = m4 + 0.5

25.       

26.        pSrc1++;

27.        pSrc2++;

28.        pDest++;

29.     }

30. }

复制代码

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

1.  void CSSETestDlg::ComputeArrayAssemblySSE(

2.           float* pArray1,                  // [输入]源数组1

3.           float* pArray2,                  // [输入]源数组2

4.           float* pResult,                  // [输出]用来存放结果的数组

5.            intnSize)                       // [输入]数组的大小

6.  {

7.      int nLoop = nSize/4;

8.      float f = 0.5f;

9.   

10.     _asm

11.     {

12.        movss   xmm2, f                        // xmm2[0]= 0.5

13.        shufps  xmm2, xmm2, 0                  // xmm2[1, 2, 3] =xmm2[0]

14.  

15.        mov         esi, pArray1               //输入的源数组1的地址送往esi

16.        mov         edx, pArray2               //输入的源数组2的地址送往edx

17.  

18.        mov         edi, pResult               //输出结果数组的地址保存在edi

19.        mov         ecx, nLoop                 //循环次数送往ecx

20.  

21. start_loop:

22.        movaps      xmm0, [esi]                // xmm0 =[esi]

23.        mulps       xmm0, xmm0                 // xmm0 = xmm0 * xmm0

24.  

25.         movaps     xmm1, [edx]                // xmm1 = [edx]

26.        mulps       xmm1, xmm1                 // xmm1 = xmm1 * xmm1

27.  

28.        addps       xmm0, xmm1                 // xmm0 = xmm0 + xmm1

29.        sqrtps      xmm0, xmm0                 // xmm0 = sqrt(xmm0)

30.  

31.        addps       xmm0, xmm2                 // xmm0 = xmm1 + xmm2

32.  

33.        movaps      [edi], xmm0                // [edi]= xmm0

34.  

35.        add         esi, 16                   // esi += 16

36.        add         edx, 16                   // edx += 16

37.        add         edi, 16                   // edi += 16

38.  

39.        dec         ecx                       // ecx--

40.        jnz         start_loop               //如果不为0则转向start_loop

41.     }

42. }

复制代码

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

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

以上的时间结果是在Release优化编译后执行程序得出的。
SSESample 示例项目
SSESample项目是一个基于对话框的应用程序,其中它用下面的浮点数数组进行计算:
fResult = sqrt(fSource*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.  voidCSSESampleDlg::OnBnClickedButtonCplusplus()

4.  {

5.      m_fMin = FLT_MAX;

6.      m_fMax = FLT_MIN;

7.   

8.      int i;

9.   

10.     for ( i = 0; i <ARRAY_SIZE; i++ )

11.     {

12.        m_fResultArray[i] = sqrt(m_fInitialArray[i]  *2.8f);

13.  

14.         if (m_fResultArray[i] < m_fMin )

15.            m_fMin = m_fResultArray[i];

16.  

17.         if (m_fResultArray[i] > m_fMax )

18.            m_fMax = m_fResultArray[i];

19.     }

20. }

复制代码

使用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.   

8.      __m128 min128 =_mm_set_ps1(FLT_MAX);  // min128[0, 1, 2, 3] = FLT_MAX

9.      __m128 max128 =_mm_set_ps1(FLT_MIN);  // max128[0, 1, 2, 3] = FLT_MIN

10.  

11.     __m128* pSource = (__m128*)m_fInitialArray;

12.     __m128* pDest = (__m128*)m_fResultArray;

13.  

14.     for ( int i = 0; i <ARRAY_SIZE/4; i++ )

15.     {

16.        tmp = _mm_mul_ps(*pSource, coeff);     // tmp = *pSource * coeff

17.        *pDest= _mm_sqrt_ps(tmp);             // *pDest = sqrt(tmp)

18.  

19.         min128=  _mm_min_ps(*pDest, min128);

20.        max128 =  _mm_max_ps(*pDest, max128);

21.  

22.        pSource++;

23.        pDest++;

24.     }

25.  

26.     // 计算max128的最大值和min128的最小值

27.     union u

28.     {

29.        __m128 m;

30.         floatf[4];

31.     } x;

32.  

33.     x.m = min128;

34.     m_fMin =min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));

35.  

36.     x.m = max128;

37.     m_fMax = max(x.f[0],max(x.f[1], max(x.f[2], x.f[3])));

38. }

复制代码

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

1.  // 输入: m_fInitialArray

2.  // 输出: m_fResultArray, m_fMin, m_fMax

3.  voidCSSESampleDlg::OnBnClickedButtonSseAssembly()

4.  {

5.    

6.      float* pIn = m_fInitialArray;

7.      float* pOut = m_fResultArray;

8.   

9.      float f = 2.8f;

10.     float flt_min = FLT_MIN;

11.     float flt_max = FLT_MAX;

12.  

13.     __m128 min128;

14.     __m128 max128;

15.  

16.     // 使用以下的附加寄存器:xmm2、xmm3、xmm4:

17.     // xmm2 – 相乘系数

18.     // xmm3 – 最小值

19.     // xmm4 – 最大值

20.  

21.     _asm

22.     {

23.        movss   xmm2, f                        //xmm2[0] = 2.8

24.        shufps  xmm2, xmm2, 0                  // xmm2[1, 2, 3] = xmm2[0]

25.  

26.        movss   xmm3, flt_max                  // xmm3 = FLT_MAX

27.        shufps  xmm3, xmm3, 0                  // xmm3[1, 2, 3] =xmm3[0]

28.  

29.        movss   xmm4, flt_min                  // xmm4 = FLT_MIN

30.         shufps  xmm4,xmm4, 0                  // xmm3[1, 2, 3] = xmm3[0]

31.  

32.        mov         esi, pIn                  //输入数组的地址送往esi

33.        mov         edi, pOut                  //输出数组的地址送往edi

34.        mov         ecx,ARRAY_SIZE/4           //循环计数器初始化

35.  

36. start_loop:

37.        movaps      xmm1, [esi]                // xmm1 =[esi]

38.        mulps       xmm1, xmm2                 // xmm1 = xmm1 * xmm2

39.        sqrtps      xmm1, xmm1                 // xmm1 = sqrt(xmm1)

40.        movaps      [edi], xmm1                // [edi]= xmm1

41.  

42.        minps       xmm3, xmm1

43.        maxps       xmm4, xmm1

44.  

45.        add         esi, 16

46.        add         edi, 16

47.  

48.        dec         ecx

49.        jnz         start_loop

50.  

51.  

52.        movaps      min128, xmm3

53.        movaps      max128, xmm4

54.     }

55.  

56.     union u

57.     {

58.        __m128 m;

59.         floatf[4];

60.     } x;

61.  

62.     x.m = min128;

63.     m_fMin =min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));

64.  

65.     x.m = max128;

66.     m_fMax = max(x.f[0],max(x.f[1], max(x.f[2], x.f[3])));

67. }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值