【转】【MMX】 基于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 //对数组中的每一个元素b
  b = b + n //加上一个数n
下面看看它的实现细节:
for each b in array //对数组中的每一个元素b
{
  把b加载到寄存器中
  把此寄存器中的数加上n
  把所得寄存器中的结果放回内存
}
具有MMX指令集支持的处理器有八个64位的寄存器,每一个寄存器可以存放8个字节(byte)、4个字(word)或2个双字(double-word)。MMX技术同时提供了一个MMX指令集,其中的指令可以可以把一个数值(其类型可以是字节、字或双字)加载到这些MMX寄存器中,在寄存器中进行算术或逻辑运算,然后把寄存器中的结果放回内存存储单元。上面的例子采用MMX技术后的算法是这样的:
for each 8 members in array //把数组中的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); // 饱和模式下的无符号减法
//对每一个字节执行操作: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 ) //增加象素颜色值
{
for ( i = 0; i < nNumberOfPixels; i++ )
{
n = (int)(*pSource++ + b);

if ( n > 255 )
n = 255;

*pDest++ = (BYTE) n;
}
}
else //减少象素颜色值
{
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++; //取下面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 ); //将tmp中低位的4个字节转化为字
//字的高位用nNull中对应位上的位值填充。 

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

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

tmp = _mm_packs_pu16 (tmp, nNull); // 使用饱和模式将tmp中的结果做如下处理:
//将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指令对整型数操作的函数。

参考文档:
[1] Intel软件说明书(Intel Software manuals):http://developer.intel.com/design/archives/processors/mmx/index.htm 。
[2] MSDN中有关MMX技术的主题:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/vcrefsupportformmxtechnology.asp
[3] Microsoft Visual C++ CPUID项目示例:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/vcsamcpuiddeterminecpucapabilities.asp
[4] Microsoft Visual C++ MMXSwarm项目示例:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcsample/html/vcsamMMXSwarmSampleDemonstratesCImageVisualCsMMXSupport.asp。
[5] Matt Pietrek在Microsoft Systems Journal 1998年2月刊上的评论文章:http://www.microsoft.com/msj/0298/hood0298.aspx

转载至:http://blog.itpub.net/8781179/viewspace-924611/

时间: 2024-10-01 19:53:52

【转】【MMX】 基于MMX指令集的程序设计简介的相关文章

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

基于SSE指令集的程序设计简介 作者:Alex Farber 出处:http://www.codeproject.com/cpp/sseintro.asp SSE技术简介 Intel公司的单指令多数据流式扩展(SSE,Streaming SIMD Extensions)技术能够有效增强CPU浮点运算的能力.Visual Studio .NET 2003提供了对SSE指令集的编程支持,从而允许用户在C++代码中不用编写汇编代码就可直接使用SSE指令的功能.MSDN中有关SSE技术的主题[1]有可能

【转】【SEE】基于SSE指令集的程序设计简介

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

javascript高级程序设计--简介

工作一年多了,这一年的收获真是丰富.结识了许多同事朋友,技术网友,学了许多新的技术知识:当然还要感谢我的朋友们,感谢我的第一家公司. 大学主要学的.net,刚毕业那会对javascript的了解几乎就是空白,后来有机会接触了百度地图,开始了javascript学习之路.现在在项目中也能熟练的使用javascript,jquery等技术,可总感觉缺点什么,有些东西你知道是什么,也能写出来,但就是不知道他是什么,他的原理是什么样的.所以才有了现在的想法,系统的学习一遍javascript.---文笔

[推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下)

原文:[推荐]ORACLE PL/SQL编程详解之一:PL/SQL 程序设计简介(千里之行,始于足下) [推荐]ORACLE PL/SQL编程详解之一: PL/SQL 程序设计简介(千里之行,始于足下) ——通过知识共享树立个人品牌. 继上六篇: [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [推荐]ORACLE PL/SQL编程详解之三:PL/SQL流程控制语句(不给规则,不成方圆) [推荐]ORACLE PL/SQL编程之四:把游标说透(

Lucene:基于Java的全文检索引擎简介

Lucene是一个基于Java的全文索引工具包. 基于Java的全文索引引擎Lucene简介:关于作者和Lucene的历史 全文检索的实现:Luene全文索引和数据库索引的比较 中文切分词机制简介:基于词库和自动切分词算法的比较 具体的安装和使用简介:系统结构介绍和演示 Hacking Lucene:简化的查询分析器,删除的实现,定制的排序,应用接口的扩展 从Lucene我们还可以学到什么 另外,如果是在选择全文引擎,现在也许是试试 Sphinx的时候了:相比Lucene速度更快, 有中文分词的

基于EEPlat的项目开发过程简介

EEPlat平台比较匹配敏捷式开发过程(如XP),可以进行简单设计快速迭代,基本可以以一周或两周作为迭代周期.当然了EEPlat也可以支持传统重量级的软件开发过程(如RUP等). 项目开始初期,与传统项目一样进行需求调研,进行需求分析,经过和客户讨论确定后可以获得一个相对完整的软件需求.然后进行简单设计,进行功能分析.流程分析.数据分析.基于EEPlat开发首先需要完成数据库设计,数据库设计完成后,即可进行开发.功能分析和流程分析相对比较简单即可,后期平台在这方面的调整会很快,完全可以通过与客户

PL/SQL 程序设计简介

 ①PL/SQL 程序设计简介 PL/SQL是一种高级数据库程序设计语言,该语言专门用于在各种环境下对ORACLE数据库进行访问.由于该语言集成于数据库服务器中, 所以PL/SQL代码可以对数据进行快速高效的处理 在PL/SQL中可以使用的SQL语句有: INSERT,UPDATE,DELETE,SELECT -INTO,COMMIT,ROLLBACK,SAVEPOINT. 提示:在PL/SQL中只能用SQL语句中的DML 部分,不能用DDL 部分,如果要在PL/SQL中使用DDL(如CRE

MT3332基于主机的解决方案技术简介免费下载

今天分享的MT3332的基础资料和基于主机的解决方案技术简介,资料太多,不方便上传,有兴趣的朋友可以到闯客网技术论坛下载,或者加群获取更多联发科芯片资料:813238832 一般描述MT3332是一种高性能的单芯片多GNSS解决方案,包括片上CMOS RF和数字基带.它能够达到业界最高水平的灵敏度.准确度和第一次修复的时间(TTFF)的最小功耗的小脚印无铅封装.其占地面积小,BOM要求最低,为设计提供了显著的减少.便携式应用所需的制造和测试资源. MT332即使在室内信号电平下也能在最短的时间内

【学习笔记】第1讲-C#程序设计简介-1.1 C#与.NET的简介

1.1 C#与.NET的简介 C#语言简介 *历史 C -> C++ -> Java -> C# (C语言60年代开始的,现在用途还很广泛.但是C语言不是面向对象的,到后面C++加了很多面向对象的特点,C++内容复杂且多,紧接着出现的就是Java语言,Java语言的出现被誉为C+ + - -,它把C++里面最复杂的部分去掉一部分,就形成了Java语言.Java语言1995年创建以来取得了比较大的成功.后来微软又出了一个C#语言,C#是微软公司在2000年7月发布的一种全新且简单.安全.面