基于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技术在Visual Studio .NET 2003下的应用实例,你可以在
http://www.codeproject.com/cpp/sseintro/SSE_src.zip下载示例程序压缩包。该压缩包中含有两个项目,这两个项目是基于微软基本类库(MFC)建立的Visual C++.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数组)通过使用sin和cos函数给它赋值,我们用Kris Jearakul开发的瀑布状图表控件(Waterfall chart control)[3] 来显示参与计算的源数组和结果数组。计算所需的时间(以毫秒ms为单位)在对话框中显示出来。我们使用三种不同的途径来完成计算:

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


纯C++代码:

 

None.gif void CSSETestDlg::ComputeArrayCPlusPlus(
None.gif           float* pArray1,                    //  [输入] 源数组1
None.gif
           float* pArray2,                    //  [输入] 源数组2
None.gif
           float* pResult,                    //  [输出] 用来存放结果的数组
None.gif
           int nSize)                             //  [输入] 数组的大小
ExpandedBlockStart.gif
{
InBlock.gif
InBlock.gif    int i;
InBlock.gif
InBlock.gif    float* pSource1 = pArray1;
InBlock.gif    float* pSource2 = pArray2;
InBlock.gif    float* pDest = pResult;
InBlock.gif
InBlock.gif    for ( i = 0; i < nSize; i++ )
ExpandedSubBlockStart.gif    {
InBlock.gif        *pDest = (float)sqrt((*pSource1) * (*pSource1) + (*pSource2)
InBlock.gif                 * (*pSource2)) + 0.5f;
InBlock.gif
InBlock.gif        pSource1++;
InBlock.gif        pSource2++;
InBlock.gif        pDest++;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

 


下面我们用具有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指令函数的代码:

 

None.gif void CSSETestDlg::ComputeArrayCPlusPlusSSE(
None.gif           float* pArray1,                    //  [输入] 源数组1
None.gif
           float* pArray2,                    //  [输入] 源数组2
None.gif
           float* pResult,                    //  [输出] 用来存放结果的数组
None.gif
           int nSize)                         //  [输入] 数组的大小
ExpandedBlockStart.gif
{
InBlock.gif    int nLoop = nSize/ 4;
InBlock.gif
InBlock.gif    __m128 m1, m2, m3, m4;
InBlock.gif
InBlock.gif    __m128* pSrc1 = (__m128*) pArray1;
InBlock.gif    __m128* pSrc2 = (__m128*) pArray2;
InBlock.gif    __m128* pDest = (__m128*) pResult;
InBlock.gif
InBlock.gif
InBlock.gif    __m128 m0_5 = _mm_set_ps1(0.5f);        // m0_5[0, 1, 2, 3] = 0.5
InBlock.gif

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

ExpandedBlockEnd.gif}

None.gif

 

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

 

None.gif void CSSETestDlg::ComputeArrayAssemblySSE(
None.gif           float* pArray1,                    //  [输入] 源数组1
None.gif
           float* pArray2,                    //  [输入] 源数组2
None.gif
           float* pResult,                    //  [输出] 用来存放结果的数组
None.gif
           int nSize)                         //  [输入] 数组的大小
ExpandedBlockStart.gif
{
InBlock.gif    int nLoop = nSize/4;
InBlock.gif    float f = 0.5f;
InBlock.gif
InBlock.gif    _asm
ExpandedSubBlockStart.gif    {
InBlock.gif        movss   xmm2, f                         // xmm2[0] = 0.5
InBlock.gif
        shufps  xmm2, xmm2, 0                   // xmm2[1, 2, 3] = xmm2[0]
InBlock.gif

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

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

InBlock.gifstart_loop:
InBlock.gif        movaps      xmm0, [esi]                 // xmm0 = [esi]
InBlock.gif
        mulps       xmm0, xmm0                  // xmm0 = xmm0 * xmm0
InBlock.gif

InBlock.gif        movaps      xmm1, [edx]                 // xmm1 = [edx]
InBlock.gif
        mulps       xmm1, xmm1                  // xmm1 = xmm1 * xmm1
InBlock.gif

InBlock.gif        addps       xmm0, xmm1                  // xmm0 = xmm0 + xmm1
InBlock.gif
        sqrtps      xmm0, xmm0                  // xmm0 = sqrt(xmm0)
InBlock.gif

InBlock.gif        addps       xmm0, xmm2                  // xmm0 = xmm1 + xmm2
InBlock.gif

InBlock.gif        movaps      [edi], xmm0                 // [edi] = xmm0
InBlock.gif

InBlock.gif        add         esi, 16                     // esi += 16
InBlock.gif
        add         edx, 16                     // edx += 16
InBlock.gif
        add         edi, 16                     // edi += 16
InBlock.gif

InBlock.gif        dec         ecx                         // ecx--
InBlock.gif
        jnz         start_loop                //如果不为0则转向start_loop
ExpandedSubBlockEnd.gif
    }

ExpandedBlockEnd.gif}

None.gif

 

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

纯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++代码:

 

None.gif //  输入: m_fInitialArray
None.gif
//  输出: m_fResultArray, m_fMin, m_fMax
None.gif
void CSSESampleDlg::OnBnClickedButtonCplusplus()
ExpandedBlockStart.gif {
InBlock.gif    m_fMin = FLT_MAX;
InBlock.gif    m_fMax = FLT_MIN;
InBlock.gif
InBlock.gif    int i;
InBlock.gif
InBlock.gif    for ( i = 0; i < ARRAY_SIZE; i++ )
ExpandedSubBlockStart.gif    {
InBlock.gif        m_fResultArray[i] = sqrt(m_fInitialArray[i]  * 2.8f);
InBlock.gif
InBlock.gif        if ( m_fResultArray[i] < m_fMin )
InBlock.gif            m_fMin = m_fResultArray[i];
InBlock.gif
InBlock.gif        if ( m_fResultArray[i] > m_fMax )
InBlock.gif            m_fMax = m_fResultArray[i];
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif

 

 

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

 

None.gif //  输入: m_fInitialArray
None.gif
//  输出: m_fResultArray, m_fMin, m_fMax
None.gif
void CSSESampleDlg::OnBnClickedButtonSseC()
ExpandedBlockStart.gif {
InBlock.gif    __m128 coeff = _mm_set_ps1(2.8f);      // coeff[0, 1, 2, 3] = 2.8
InBlock.gif
    __m128 tmp;
InBlock.gif
InBlock.gif    __m128 min128 = _mm_set_ps1(FLT_MAX);  // min128[0, 1, 2, 3] = FLT_MAX
InBlock.gif
    __m128 max128 = _mm_set_ps1(FLT_MIN);  // max128[0, 1, 2, 3] = FLT_MIN
InBlock.gif

InBlock.gif    __m128* pSource = (__m128*) m_fInitialArray;
InBlock.gif    __m128* pDest = (__m128*) m_fResultArray;
InBlock.gif
InBlock.gif    for ( int i = 0; i < ARRAY_SIZE/4; i++ )
ExpandedSubBlockStart.gif    {
InBlock.gif        tmp = _mm_mul_ps(*pSource, coeff);      // tmp = *pSource * coeff
InBlock.gif
        *pDest = _mm_sqrt_ps(tmp);              // *pDest = sqrt(tmp)
InBlock.gif

InBlock.gif        min128 =  _mm_min_ps(*pDest, min128);
InBlock.gif        max128 =  _mm_max_ps(*pDest, max128);
InBlock.gif
InBlock.gif        pSource++;
InBlock.gif        pDest++;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    // 计算max128的最大值和min128的最小值
InBlock.gif
    union u
ExpandedSubBlockStart.gif    {
InBlock.gif        __m128 m;
InBlock.gif        float f[4];
ExpandedSubBlockEnd.gif    }
 x;
InBlock.gif
InBlock.gif    x.m = min128;
InBlock.gif    m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));
InBlock.gif
InBlock.gif    x.m = max128;
InBlock.gif    m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));
ExpandedBlockEnd.gif}

None.gif


 

 

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

None.gif// 输入: m_fInitialArray
None.gif
// 输出: m_fResultArray, m_fMin, m_fMax
None.gif
void CSSESampleDlg::OnBnClickedButtonSseAssembly()
ExpandedBlockStart.gif{
InBlock.gif   
InBlock.gif    float* pIn = m_fInitialArray;
InBlock.gif    float* pOut = m_fResultArray;
InBlock.gif
InBlock.gif    float f = 2.8f;
InBlock.gif    float flt_min = FLT_MIN;
InBlock.gif    float flt_max = FLT_MAX;
InBlock.gif
InBlock.gif    __m128 min128;
InBlock.gif    __m128 max128;
InBlock.gif
InBlock.gif    // 使用以下的附加寄存器:xmm2、xmm3、xmm4:
InBlock.gif    
// xmm2 – 相乘系数
InBlock.gif    
// xmm3 – 最小值
InBlock.gif    
// xmm4 – 最大值
InBlock.gif

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

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

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

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

InBlock.gifstart_loop:
InBlock.gif        movaps      xmm1, [esi]                 // xmm1 = [esi]
InBlock.gif
        mulps       xmm1, xmm2                  // xmm1 = xmm1 * xmm2
InBlock.gif
        sqrtps      xmm1, xmm1                  // xmm1 = sqrt(xmm1)
InBlock.gif
        movaps      [edi], xmm1                 // [edi] = xmm1
InBlock.gif

InBlock.gif        minps       xmm3, xmm1
InBlock.gif        maxps       xmm4, xmm1
InBlock.gif
InBlock.gif        add         esi, 16
InBlock.gif        add         edi, 16
InBlock.gif
InBlock.gif        dec         ecx
InBlock.gif        jnz         start_loop
InBlock.gif
InBlock.gif
InBlock.gif        movaps      min128, xmm3
InBlock.gif        movaps      max128, xmm4
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    union u
ExpandedSubBlockStart.gif    {
InBlock.gif        __m128 m;
InBlock.gif        float f[4];
ExpandedSubBlockEnd.gif    }
 x;
InBlock.gif
InBlock.gif    x.m = min128;
InBlock.gif    m_fMin = min(x.f[0], min(x.f[1], min(x.f[2], x.f[3])));
InBlock.gif
InBlock.gif    x.m = max128;
InBlock.gif    m_fMax = max(x.f[0], max(x.f[1], max(x.f[2], x.f[3])));
InBlock.gif
ExpandedBlockEnd.gif}
目录
相关文章
|
6月前
|
存储 程序员
指令系统简介
一、指令系统简介 指令系统是计算机硬件和软件之间的接口,它定义了计算机能够理解和执行的指令集合。指令系统决定了计算机的操作范围、功能和性能。 指令系统包括以下几个方面: 1. 指令集:指令集是计算机能够执行的指令的集合。它定义了计算机能够完成的操作,如算术运算、逻辑运算、数据传输等。指令集可以分为简单指令集和复杂指令集两种类型。 - 简单指令集:包括基本的算术和逻辑操作指令,如加法、减法、与、或等。这种指令集的优点是指令简单,易于实现和执行,但功能相对有限。 - 复杂指令集:除了基本的算术和逻辑操作指令外,还包括更复杂的指令,如乘法、除法、浮点运算等。这种指令集的优点是功能丰富,可以完成更复杂
110 0
|
29天前
|
C语言 异构计算
|
存储 C语言
《PIC微控制器项目设计:C语言》一3.3.2 XC8语言的特性
本文讲的是PIC微控制器项目设计:C语言一3.3.2 XC8语言的特性,本节书摘来华章计算机《PIC微控制器项目设计:C语言》一书中的第3章,第3.3.2节, PIC Microcontroller Projects in C: Basic to Advanced, Second Edition〔塞浦路斯〕 多甘·易卜拉欣(Dogan Ibrahim) 著许辉 吕汶译 译更多章节内容可以访问云栖社区“华章计算机”公众号查看。
2133 0
|
8月前
|
存储 芯片
ARM简单程序设计【嵌入式系统】
ARM简单程序设计【嵌入式系统】
101 0
|
10月前
|
缓存 前端开发 Java
汇编语言简介
汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。普遍地说,特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。
|
11月前
|
存储 安全 编译器
MIPS架构深入理解11-向MIPS移植软件之编程语言
MIPS架构深入理解11-向MIPS移植软件之编程语言
|
缓存 Linux 编译器
ARM嵌入式开发——基础概念
ARM嵌入式开发——基础概念
173 0
|
存储 缓存 安全
平头哥 CPU 编程模型、指令集|学习笔记
快速学习平头哥 CPU 编程模型、指令集
1037 0
平头哥 CPU 编程模型、指令集|学习笔记
|
芯片 异构计算
FPGA原理和结构- 理解FPGA的基础知识
FPGA原理和结构- 理解FPGA的基础知识
298 0
FPGA原理和结构- 理解FPGA的基础知识