登录社区:用户名: 密码: 忘记密码 网页功能:加入收藏 设为首页 网站搜索  

文档

下载

图书

论坛

安全

源码

硬件

游戏
首页 信息 空间 VB VC Delphi Java Flash 补丁 控件 安全 黑客 电子书 笔记本 手机 MP3 杀毒 QQ群 产品库 分类信息 编程网站
 内容搜索 网页 下载 源代码
热点文章
  SSE 介紹
  浮点数到整数的快速转换
  基于SSE指令集的程序设计简介
  CPU 的 cache 和 latency
  关键字 对齐 内存对齐
  INTEL 体系结构 MMX™ ..
  在非MFC程序中使用调试宏 AS..
  Microsoft Visual C++ 浮点优..
  INTEL X86 体系 32 位汇编语..
  加速&优化技术
  加快程序运行速度的技巧
  VC代码的编写和调试
本站原创
您现在的位置:立华软件园->游戏开发->程序设计->优化调试
基于SSE指令集的程序设计简介
发表日期:2006-08-28作者:[转贴] 出处:  

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

    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汇编指令 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指令函数的代码:

    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 毫秒
使用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++代码:

    // 输入: 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];
        }
    }



 使用Visual C++.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;

        // 使用以下的附加寄存器:xmm2、xmm3、xmm4:
        // 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])));
    }



参考文档:

[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 。

我来说两句】 【发送给朋友】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 基于SSE指令集的程序设计简介

 ■ [欢迎对本文发表评论]
用  户:  匿名发出:
您要为您所发的言论的后果负责,故请各位遵纪守法并注意语言文明。

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放 / 友情链接

Copyright ©2001-2003 Allrights reserved
e_mail:站长:webmaster(at)lihuasoft.net
网站编程QQ群  
京ICP备05001064号

页面生成时间:0.00459