`

MMX编程简介

阅读更多
MMX技术简介

Intel 公司的MMX™(多媒体增强指令集)技术可以大大提高应用程序对二维三维图形和图象的处理能力。Intel MMX技术可用于对大量数据和复杂数组进行的复杂处理,使用MMX技术可处理的数据基本单位可以是字节(byte)、字(word),或者是双字(double-word)。
Visual Studio .NET 2003提供了对MMX指令集特性的支持,从而可以不必编写汇编代码,直接使用C++代码就可以实现MMX指令的功能。通过参考Intel软件说明书(Intel Software manuals)[1]以及阅读MSDN中有关MMX编程技术的主题会使你更好地把握MMX编程的要点。

MMX技术实现了单道指令多道数据流(SIMD,single-instruction, multiple-data)的执行模式。考虑下面一个需要编程完成的任务,在一个字节(BYTE)数组中使其中每一个元素加上一个数,在传统的程序中,实现这个功能的算法如下:

for each b in array     file://对数组中的每一个元素b
    b = b + n            file://加上一个数n

下面看看它的实现细节:

for each b in array file://对数组中的每一个元素b
{
    把b加载到寄存器中
    把此寄存器中的数加上n
    把所得寄存器中的结果放回内存
}


具有MMX指令集支持的处理器有八个64位的寄存器,每一个寄存器可以存放8个字节(byte)、4个字(word)或2个双字(double-word)。MMX技术同时提供了一个MMX指令集,其中的指令可以可以把一个数值(其类型可以是字节、字或双字)加载到这些MMX寄存器中,在寄存器中进行算术或逻辑运算,然后把寄存器中的结果放回内存存储单元。上面的例子采用MMX技术后的算法是这样的:

for each 8 members in array file://把数组中的8个字节(其中一个字节为数组中的一个单位)作为一组取出
{
    把这8个字节加载到MMX寄存器中
    通过一个CPU指令执行周期把这个寄存器中的8个字节都加上n
    把寄存器中计算的结果写回内存
}

C++编程人员不必直接使用MMX指令集中的指令访问这些MMX寄存器。你可以使用64位的数据类型__m64和一系列C++函数来进行相关的算术和逻辑运算。而决定程序使用哪个MMX寄存器以及代码优化是C++编译器的任务。

Visual C++ MMXSwarm [4]是MSDN中提供的一个很好的使用MMX技术进行图象处理的例子,它包含了一些封装好了的类简化了使用MMX技术的操作,并向你展示了对各种不同格式图象进行处理的操作(如单色24位象素RGB、32位象素RGB等)。本文只是对使用Visual C++实现MMX程序设计的简单介绍。如果你感兴趣的话,可以参看MSDN上MMXSwarm的例子。

MMX程序设计详细介绍

包含的头文件

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

__m64 数据类型

这种类型的变量可用作MMX指令的操作数,它不能被直接访问。_m64类型的变量被自动分配为8个字节的字长。

CPU对MMX指令集的支持

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

饱和算法(Saturation Arithmetic)和封装模式(Wraparound Mode)

MMX技术支持一种叫做saturating arithmetic(饱和算法)的计算模式。在饱和模式下,当计算结果发生溢出(上溢或下溢)时,CPU会自动去掉溢出的部分,使计算结果取该数据类型表示数值的上限值(如果上溢)或下限值(如果下溢)。饱和模式的计算用于对图象的处理。

下面的例子能够让你理解饱和模式和封装模式的区别。如果一个字节(BYTE)类型变量的值为255,然后将其值加一。在封装模式下,相加结果为0(去掉进位);在饱和模式下,结果为255。饱和模式用类似的方法来处理下溢出,比如对于一个字节数据类型的数在饱和模式下,1减2的结果为0(而不是-1)。每一个MMX算术指令都有这两种模式:饱和模式和封装模式。本文所要讨论的项目只使用饱和模式下的MMX指令。

编程实例

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

MMX8 演示项目

MMX8是一个单文档界面(SDI)的应用程序,用来对每象素8位的单色位图进行简单处理。源图象和处理后的图象会在窗体中显示出来。新建的ATL(活动模版库)类 Cimage用来从资源中提取图象并在窗体中显示出来。程序要对图象进行两种处理操作:图象颜色反相和改变图象的亮度。每一种处理操作可以用下面几种方法之中其中的一种来实现:

纯C++代码;
使用C++的MMX功能函数的代码;
使用MMX汇编指令的代码。

对图象进行处理计算的时间会显示在状态栏中。

用纯C++实现的图象颜色反相函数:

void CImg8Operations::InvertImageCPlusPlus(
    BYTE* pSource,
    BYTE* pDest,
    int nNumberOfPixels)
{
    for ( int i = 0; i < nNumberOfPixels; i++ )
    {
        *pDest++ = 255 - *pSource++;
    }
}


为了查询使用C++ MMX指令函数的方法,需要参考Intel软件说明书(Intel Software manuals)中有关MMX汇编指令的说明,首先我是在第一卷的第八章找到了MMX相关指令的大体介绍,然后在第二卷找到了有关这些MMX指令的详细说明,这些说明有一部分涉及了与其特性相关的C++函数。然后我通过这些MMX指令对应的C++函数查找了MSDN中与其相关的说明。在MMX8示例程序中用到的MMX指令和相关的C++函数见下表:


实现的功能 对应的MMX汇编指令 Visual C++.NET中的MMX函数
清除MMX寄存器中的内容,即初始化(以避免和浮点数操作发生冲突)。 emms _mm_empty
将两个64位数中对应的(8个)无符号(8位)字节同时进行减法操作。 psubusb _mm_subs_pu8
将两个64位数中对应的(8个)无符号(8位)字节同时进行加法操作。 paddusb _mm_adds_pu8

用Visual C++.NET的MMX指令函数实现图象颜色反相的函数:

void CImg8Operations::InvertImageC_MMX(
    BYTE* pSource,
    BYTE* pDest,
    int nNumberOfPixels)
{
    __int64 i = 0;
    i = ~i;                                 // 0xffffffffffffffff  

    // 每次循环处理8个象素
    int nLoop = nNumberOfPixels/8;

    __m64* pIn = (__m64*) pSource;          // 输入的字节数组指针
    __m64* pOut = (__m64*) pDest;           // 输出的字节数组指针

    __m64 tmp;                              // 临时工作变量

    _mm_empty();                            // 执行MMX指令:emms,初始化MMX寄存器

    __m64 n1 = Get_m64(i);

    for ( int i = 0; i < nLoop; i++ )
    {
        tmp = _mm_subs_pu8 (n1 , *pIn);     // 饱和模式下的无符号减法
                                            file://对每一个字节执行操作:tmp = n1 - *pIn
        *pOut = tmp;

        pIn++;                              // 取下面的8个象素点
        pOut++;
    }

    _mm_empty();                            // 执行MMX指令:emms,清除MMX寄存器中的内容
}

__m64 CImg8Operations::Get_m64(__int64 n)
{
    union __m64__m64
    {
        __m64 m;
        __int64 i;
    } mi;

    mi.i = n;
    return mi.m;
}

虽然这个函数在非常短的时间就执行完成了,但我记录了这3种方法需要的时间,以下是在我的计算机上运行的结果:

纯C++代码      43毫秒
使用C++的MMX指令函数的代码 26毫秒
使用MMX汇编指令的代码   26毫秒

上面的图象处理时间必须在程序Release优化编译后执行时才能体现出很好的效果。

而改变图象的亮度我采用了最简单的方法:对图象中的每一个象素的颜色值进行加减运算。相对前面的处理函数而言,这样的转换函数有些复杂,因为我们需要把处理过程分成两种情况,一种是增加象素颜色值,另一种是减少象素颜色值。


用纯C++函数实现的改变图象亮度的函数:

void CImg8Operations::ChangeBrightnessCPlusPlus(
    BYTE* pSource,
    BYTE* pDest,
    int nNumberOfPixels,
    int nChange)
{
    if ( nChange > 255 )
        nChange = 255;
    else if ( nChange < -255 )
        nChange = -255;

    BYTE b = (BYTE) abs(nChange);

    int i, n;

    if ( nChange > 0 ) file://增加象素颜色值
    {
        for ( i = 0; i < nNumberOfPixels; i++ )
        {
            n = (int)(*pSource++ + b);

            if ( n > 255 )
                n = 255;

            *pDest++ = (BYTE) n;
        }
    }
    else    file://减少象素颜色值
    {
        for ( i = 0; i < nNumberOfPixels; i++ )
        {
            n = (int)(*pSource++ - b);

            if ( n < 0 )
                n = 0;
            *pDest++ = (BYTE) n;
        }
    }
}


用Visual C++.NET的MMX指令函数实现的改变图象亮度函数:

void CImg8Operations::ChangeBrightnessC_MMX(

BYTE* pSource,
    BYTE* pDest,
    int nNumberOfPixels,
    int nChange)
{
    if ( nChange > 255 )
        nChange = 255;
    else if ( nChange < -255 )
        nChange = -255;

    BYTE b = (BYTE) abs(nChange);

    __int64 c = b;

    for ( int i = 1; i <= 7; i++ )
    {
        c = c << 8;
        c |= b;
    }

    // 在一次循环中处理8个象素
    int nNumberOfLoops = nNumberOfPixels / 8;

    __m64* pIn = (__m64*) pSource;          // 输入的字节数组
    __m64* pOut = (__m64*) pDest;           // 输出的字节数组

    __m64 tmp;                              // 临时工作变量


    _mm_empty();                            // 执行MMX指令:emms

    __m64 nChange64 = Get_m64(c);

    if ( nChange > 0 )
    {
        for ( i = 0; i < nNumberOfLoops; i++ )
        {
       tmp = _mm_adds_pu8(*pIn, nChange64);    // 饱和模式下的无符号加法
                                               // 对每一个字节执行操作:tmp = *pIn + nChange64

            *pOut = tmp;

            pIn++;                             // 取下面8个象素
            pOut++;
        }
    }
    else
    {
        for ( i = 0; i < nNumberOfLoops; i++ )
        {
         tmp = _mm_subs_pu8(*pIn, nChange64); // 饱和模式下的无符号减法
                                              // 对每一个字节执行操作:tmp = *pIn - nChange64

            *pOut = tmp;

            pIn++;                             file://取下面8个象素

            pOut++;
        }
    }

    _mm_empty();                            // 执行MMX指令:emms
}

注意参数nChange的符号每次调用函数时在循环体外只检查一次,而不是放在循环体内,那样会被检查成千上万次。下面是在我的计算机上处理图象花费的时间:

纯C++代码      49毫秒
使用C++的MMX指令函数的代码 26毫秒
使用MMX汇编指令的代码   26毫秒

MMX32 演示项目

MMX32项目可对32位象素的RGB图象进行处理。进行的图象处理工作是图象颜色反相操作和更改图象颜色的平衡度(将象素点的每一种颜色乘以一定的值)操作。

MMX的乘法实现起来比加减法复杂得多,因为乘法运算通常得出的结果的位数不再是以前位数的大小。比如,如果乘法的操作数有一个字节(8位的BYTE)大小,那么结果会达到一个字(16位的WORD)大小。这需要额外的转换,并且使用MMX汇编指令和C++代码进行图象转换花费时间的差别不是很大(时间差为5-10%)。

用Visual C++.NET的MMX指令函数实现的更改图象颜色平衡度的函数:

void CImg32Operations::ColorsC_MMX(
    BYTE* pSource,
    BYTE* pDest,
    int nNumberOfPixels,
    float fRedCoefficient,
    float fGreenCoefficient,
    float fBlueCoefficient)
{
    int nRed = (int)(fRedCoefficient * 256.0f);
    int nGreen = (int)(fGreenCoefficient * 256.0f);
    int nBlue = (int)(fBlueCoefficient * 256.0f);

    // 设置相乘系数
    __int64 c = 0;
    c = nRed;
    c = c << 16;
    c |= nGreen;
    c = c << 16;
    c |= nBlue;

    __m64 nNull = _m_from_int(0);           // null
    __m64 tmp = _m_from_int(0);             // 临时工作临时变量初始化

    _mm_empty();                            // 清空MMX寄存器。

    __m64 nCoeff = Get_m64(c);

    DWORD* pIn = (DWORD*) pSource;          // 输入双字数组
    DWORD* pOut = (DWORD*) pDest;           // 输出双字数组

    for ( int i = 0; i < nNumberOfPixels; i++ )
    {
        tmp = _m_from_int(*pIn);              // tmp = *pIn (在tmp的低32位写入数据)

      tmp = _mm_unpacklo_pi8(tmp, nNull );    file://将tmp中低位的4个字节转化为字
file://字的高位用nNull中对应位上的位值填充。

      tmp = _mm_mullo_pi16 (tmp , nCoeff);   file://将tmp中的每一个字相乘,将相乘结果的高位送到nCoeff,在tmp中只保留每个结果的低位。

      tmp = _mm_srli_pi16 (tmp ,;          // 将tmp中的每一个字右移8位,相当于除以256

      tmp = _mm_packs_pu16 (tmp, nNull);      // 使用饱和模式将tmp中的结果做如下处理:
                                              file://将tmp中的4个字转化为4个字节,并将这4个字节写到tmp中的低32位中
                                              // 同时,将nNull中的4个字转化为4个字节,并将这4个字节写到tmp的高32位中。

      *pOut = _m_to_int(tmp);                 // *pOut = tmp (将tmp低32位的数据放入pOut数组中)

        pIn++;
        pOut++;

    }

    _mm_empty();                       
}

你可以参看示例项目的源代码了解有关此项目的更多的细节。

SSE2 技术

SSE2技术包含有一个类似MMX中对整数操作的指令集,同时也包含128位的SSE寄存器组。比如,用SSE2技术实现更改图象颜色平衡度能够比用纯C++代码实现此功能在效率上有很大提升。SSE2同时是SSE技术的扩展,比如它不仅可以单精度浮点数数组,而且能够处理双精度浮点数数据类型的数组。用C++实现的MMXSwarm 示例项目不仅使用了MMX指令函数,而且使用了SSE2指令对整型数操作的函数。
分享到:
评论

相关推荐

    MMX SSE 编程优化资料

    MMX编程优化资料 对MMX编程非常有用,MMX,SSE优化都有用处。

    64位 微处理器系统编程和应用编程.part2 周明德编著

    3 应用编程全面而完整:既有通用编程,又有利用X86处理器的浮点和数学运算编程以及利用MMX技术和SSE2 SSE3,SSSE3的多媒体和科学计算应用编程。 4 本书包括了应用编程的详尽内容,也包含了系统编程的主要内容,具有...

    64位 微处理器系统编程和应用编程.part1 周明德编著 完整清晰版

    3 应用编程全面而完整:既有通用编程,又有利用X86处理器的浮点和数学运算编程以及利用MMX技术和SSE2 SSE3,SSSE3的多媒体和科学计算应用编程。 4 本书包括了应用编程的详尽内容,也包含了系统编程的主要内容,具有...

    MMX.zip_MMX programming_mmx

    MMX即多媒体可扩展指令集,通常用汇编编写,内嵌于C++或C,提升图象算法的执行速度。该文档介绍了MMX的指令集及编程规范。

    MMX.rar_MMX programming_mmx_visual c

    MMX指令集 微软的扩展指令集 有助于优化编程

    Catalyst半导体为可编程数字电位器(DPP增加超小型2mmx2.5mm TDFN封装.pdf

    Catalyst半导体为可编程数字电位器(DPP增加超小型2mmx2.5mm TDFN封装.pdf

    编程高手箴言(推荐)

    梁肇新开发技术总结,值得借鉴 这是我在网上能够找到的最清晰的版本,分享给大家 ...8.5 MMX的实例二:MMX类的实现方法 407 8.5.1 实现方法分析 407 8.5.2 实现步骤 407 8.5.3 检测过程 410 8.5.4 总结 416

    我的编程感悟(中文PDF)(共37M二分卷)分卷二

    全书按照作者本人学习和实践的过程,带着读者从基础的计算机知识到高级的编程技术,从非常专业的汇编优化到非常实际的项目管理进行了一次游戏开发的全景探索。 本书不仅适合游戏开发者阅读,也会给所有的开发者和...

    游戏编程参考手册及应用指令

    本教程详细的介绍了MMX指令集简介(寄存器,指令集等),C程序优化之路,INTEL 体系结构以及指令集的进步(MMX和SSE) 等内容。

    编程高手箴言.rar

    本书是作者十余年编程生涯中的技术和经验的总结。内容涵盖了从认识CPU、Windows运行机理、编程语言的运行机理,到代码的规范和风格、分析方法、调试方法和内核优化,内有作者对许多问题的认知过程和透彻的分析,以及...

    C#实用编程百例随书光盘及C#编程技巧典型案例解析随书光盘

    ·Pentium MMX 500以上的PC机,128M以上内存。 ·中英文Windows 2000/xp及以上版本的操作系统。 ·Internet Explorer5.0及以上版本或NetScape Navigator6.0及以上版本。 ·具有16色以上显示能力的显示卡和显示器。

    汇编语言编程艺术

    内容涉及到数据表示、存储器管理、各种数据类型、过程、与汇编语言相关的体系结构、控制结构、文件、宏指令、位处理指令、字符串指令、MMX指令、类和对象,以及混合语言编程等,尤其是在高级汇编语言(HLA)方面,该...

    游戏之旅--我的编程感悟【有目录】

    2.5 Apple II 上的编程之路.......................................... 39 XIII 第1 章计算机,游戏,我_ 我,云风,从二十年前的计 算机游戏萌芽阶段一路走来。计 算机,并不神秘。程序,也只是 表达计算机控制...

    我的编程感悟(中文PDF)(共37M二分卷)分卷一

    全书按照作者本人学习和实践的过程,带着读者从基础的计算机知识到高级的编程技术,从非常专业的汇编优化到非常实际的项目管理进行了一次游戏开发的全景探索。 本书不仅适合游戏开发者阅读,也会给所有的开发者和...

    汇编语言编程艺术_Randall Hyde

    内容涉及到数据表示、存储器管理、各种数据类型、过程、与汇编语言相关的体系结构、控制结构、文件、宏指令、位处理指令、字符串指令、MMX指令、类和对象,以及混合语言编程等,尤其是在高级汇编语言(HLA)方面,该...

    游戏编程指南 doc

    游戏编程指南 1 目 录 1 导 读 1 第一章 表述游戏的语言 1 1.1 VC.net概述 1 1.2 入门知识 4 1.2.1 数与数据类型 4 1.2.2 变量与常量 4 1.2.3 Namespace 5 1.2.4 操作符与表达式 6 1.3 预编译指令 7 1.4 结构,...

    编程高手箴言-源码

    梁肇新的《编程高手箴言》附书源码,除去了豪杰公司的软件 内容: 2:4GMEM 3:cpu降温、thread_draw 4: hook demo等 5: cputype等 6:game_modify等 7:plugin 8:MMX等

    编程高手箴言(中文完整版)(13M)

    ” 梁肇新 《编程高手箴言》这本书就是梁肇新自己十余年来编程经验的集结。在名人出书成为热潮的这几年中,拥有丰富经历和感悟的梁肇新却没有跟风,这本《箴言》是他的第一本著作,可谓厚积薄发。全书通篇没有时髦的...

    汇编语言编程环境

    汇编编程软件内容涉及到数据表示、存储器管理、各种数据类型、过程、与汇编语言相关的体系结构、控制结构、文件、宏指令、位处理指令、字符串指令、MMX指令、类和对象,以及混合语言编程等,尤其是在高级汇编语言...

    69、单片机双路可编程温度控制板电路.rar

    一、尺寸:长72mmX宽99mmX高20mm 二、 主要芯片:单片机、DS18B20、数码管 三、 工作电压:输入电压小于12V,另有24V 可选。功耗小于2W 四、 特点: 1、具有输出电压指示灯。 2、输出具有指示灯。 3、采用螺旋压接...

Global site tag (gtag.js) - Google Analytics