C#基于MMX指令集的程序设计简介之二(转)

MMX程序设计详细介绍

包含的头文件

所有的MMX指令集函数在emmintrin.h文件中定义:
#include
因为程序中用到的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毫秒
[@more@] C#基于MMX指令集的程序设计简介之一(转)

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/8781179/viewspace-924612/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/8781179/viewspace-924612/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值