从今天起开始介绍一下MMX指令集。
MMX技术大家肯定是如雷贯耳,也不用我多废话了。总之,它是SIMD(单指令多数据)技术的一个最简单的部分,它只能对整数(BYTE、WORD、DWORD、QWORD)进行操作,而且提供的指令也有限,不象Intel原来的指令集那么复杂。但是我们可敬的雷锋叔叔不是说过“要把有限的生命投入到无限的为人民服务之中去”么?我们这里也是一样,要把有限的指令应用到无限的计算方法当中去。简单有限的指令组合起来,一样可以完成近乎无限的功能。不信可以去Intel的网站()看看,那里有非常多的应用MMX技术来优化了的算法,包罗了图像、图形、音频、通讯、信号处理等几乎所有现在比较流行的技术。当然SSE和SSE2又更进了一步,不过我们现在是讨论MMX,先把这个搞定对于理解其他的SIMD技术肯定是大有帮助。
好了,废话少说,开始进入正题。 先来讲一下MMX寄存器。MMX寄存器实际上用的就是浮点寄存器(就是原来的ST0、……、ST7)的低64Bit(至少一直到PII还是这样,PIII是不是这样我就不知道了),分别被命名为MM0、MM1、……、MM7,每个寄存器都可以被看作是8个BYTE,4个WORD、2个DWORD或1个QWORD,具体以什么为单位取决于所用的指令。MMX指令可以把这几个数据在一条指令里同时操作,而且相互间各不相干,例如下图所示: ┌───┬───┬───┬───┐ │ A1 │ A2 │ A3 │ A4 │ └───┴───┴───┴───┘ ┌───┬───┬───┬───┐ │ B1 │ B2 │ B3 │ B4 │ +└───┴───┴───┴───┘ ────────────────── ┌───┬───┬───┬───┐ │A1+B1 │A2+B2 │A3+B3 │A4+B4 │ └───┴───┴───┴───┘ 这对数据密集型的计算(比如图像处理)是个相当大的喜讯。但是因为它实际使用的是浮点寄存器,所以不能和浮点运算同时执行(占了人家的房子当然不好意思了),要执行浮点指令前必须执行一条EMMS指令,通知CPU“我的租期到了,现在该把房子还给你了”。这么一条指令也是要花时钟周期的(可能还绝对不止一个时钟周期),要是频繁的切换MMX状态和浮点状态,从MMX技术获得的这么点利润就又全陪进去了。所以在写代码的时候要注意,务必使MMX指令的运算和浮点运算分开来,个人管个人,免得一个房子两个人挤。
下面介绍MMX指令。 在MMX指令集里加入了几个原来Intel指令集里没有(当然没有了,我指的是没有类似的运算指令)的非常好用的指令,我以前搞过图像处理,最能理解这几个指令的好处,就先从这几个指令讲起。
1.饱和运算 所谓饱和运算,就是当运算结果大于一个上限或小于一个下限时,结果就等于上限或是下限。例如:BYTE运算,最大值是255,0xF1+0x35应该是等于0x26,但由于结果大于255,那么饱和运算的结果就是0xFF。在图像处理里经常有(比如说增加亮度)两种灰度值运算后要判断只是否大于255或小于0,根据结果再取255或0,又是if又是什么的。现在只要一条指令就OK了。 这几条指令分别是:
PADDS[B,W] |
饱和有符号数加[byte, word] |
PADDUS[B,W] |
饱和无符号数加[byte, word] |
PSUBS[B,W] |
饱和有符号数减[byte, word] |
PSUBUS[B,W] |
饱和无符号数减[byte, word] | 是不是很方便啊!(有符号数就是有正有负,一个BYTE就是-128~127;无符号数就是都是正的,一个BYTE就是0~255) 注:PADDS[B,W]的意思就是PADDSB和PADDSW的简写,以下都将这样写。
先讲到这里,下一篇再接着讲。(敲的累死了,以后肯定不写书了)
| |
|
(上一篇忘记说明了,这些所有的MMX指令的目的操作数——就是放在前面的——一般都是MMX寄存器,源操作数——就是放在后面的——一般是MMX寄存器或内存,以下除非有特殊说明,均有这个限制)
2.向量点乘指令 什么是向量点乘我想不用我说了吧,学过高数的都该知道。这条指令的格式如下:
其实际操作为:┌───┬───┬───┬───┐
│ A1 │ A2 │ A3 │ A4 │
└───┴───┴───┴───┘
┌───┬───┬───┬───┐
│ B1 │ B2 │ B3 │ B4 │
└───┴───┴───┴───┘ PMADDWD
──────────────────────
┌───────┬───────┐
│ A1*B1+A2*B2 │ A3*B3+A4*B4 │
└───────┴───────┘
这条指令的用处不要我说了,高数里有好多这种运算。我只是感叹,这么好用的指令为什么Intel不早点想出来呢?
3.串比较指令 这类指令有两个
PCMPEQ[B,W,D] |
串等于比较(byte,word,dword) |
PCMPGT[B,W,D] |
串大于比较(byte,word,dword) | 举个例子说明实际操作┌───┬───┬───┬───┐
│ 0xF0 │ 0xA2 │ 0x24 │ 0x75 │
└───┴───┴───┴───┘
┌───┬───┬───┬───┐
│ 0xF0 │ 0x2C │ 0x24 │ 0x54 │
└───┴───┴───┴───┘PCMPEQ
─────────────────────
┌───┬───┬───┬───┐
│ 0xFF │ 0x00 │ 0xFF │ 0x00 │
└───┴───┴───┴───┘
┌───┬───┬───┬───┐
│ 100 │ 70 │ 6 │ 18 │
└───┴───┴───┴───┘
┌───┬───┬───┬───┐
│ 40 │ 120 │ 5 │ 26 │
└───┴───┴───┴───┘PCMPGT
─────────────────────
┌───┬───┬───┬───┐
│ 0xFF │ 0x00 │ 0xFF │ 0x00 │
└───┴───┴───┴───┘
看明白了?结果为全1,即说明比较结果为TRUE;结果为全0,说明比较结果为FALSE。这条指令用在什么地方?字符串快速比较(Intel网站上有源码,扬言速度提高50%)、模拟Color Key(具体怎么实现不在本文讨论范围之内,自己想一想,我以后可能会介绍一下)等等好多方面。
接下来的指令就比较普通了,但还是有非常大的便宜可以占的。
4.数据传输
MOV[D,Q] |
数据传输(dword,qword) | MOVD就是传32个Bit(传到MMX寄存器的低32Bit或只传输MMX寄存器的低32Bit),MOVQ就是传64个Bit,这条指令是可以在MMX寄存器、通用寄存器(就是EAX、ESI之类,但是这些寄存器只能用在MOVD里——一直到PIII都还是32Bit的通用寄存器嘛)和内存之间来回折腾的,没什么限制。
今天就到这儿吧。
| |
|
5.算术指令 这里的指令和普通的算术指令没有什么运算规则上的区别,只不过是在一条指令里同时处理几个数据而已。指令如下:
PADD[B,W,D] |
无符号数加[byte,word,dword] |
PADDS[B,W] |
有符号数加[byte,word] |
PSUB[B,W,D] |
无符号数减[byte,word,dword] |
PSUBS[B,W] |
有符号数减[byte,word] |
PMULHW |
有符号数乘取高位[word] |
PMULLW |
有符号数乘取低位[word] |
值的提一下的是那两个乘法指令,因为两个WORD相乘,结果是个DWORD,但在MMX指令里又要保持数据的一致性,不可能是交给它4个数据,运算完了就剩下2个数据了,于是Intel就搞了这么个方法,乘法嘛,要算两次,一次只把高位给你,另一次再把低位给你——怎么,不服气么?哼,我给了你这么多方便又好用的指令,才讹了你这么点儿时钟周期就不满意了?CPU是我做还是你做?哼,没话说了吧!——没办法,人家是老大,该怎样就怎样了。不过凡事要往两方面想,假如我们要判断两数是否同号,只要乘完了取高位就行了,低位嘛,在这儿用不上,随它怎么样了,而且还省了个寄存器。 这两条指令的实际操作如下(只写一条就行了): ┌───────┬───┬───┐
│ A1 │ … │ … │
└───────┴───┴───┘
┌───────┬───┬───┐
│ B1 │ … │ … │
└───────┴───┴───┘PMULHW
─────────────────────
┌───────┬───┬───┐
│HIWORD(A1*B1) │ … │ … │
└───────┴───┴───┘
但是大家发现没有,这里没有除法运算,也没有无符号数的乘法运算,不要急,PIII的SSE指令集里就有了,大家升级CPU啊!
6.逻辑指令 这些指令和原来我们学过的8086汇编里的逻辑指令完全相同(除了PANDN相当于原先的两个操作——与和非——的结合),只是把寄存器换成64位的而已。
PAND |
按位与操作 |
PANDN |
按位与后再取非操作 |
POR |
按位或操作 |
PXOR |
按位异或操作 |
7.移位指令 移位指令也不要我多说了,实际就是更方便(方便嘛当然就要有点儿限制了)的乘除法运算。但是却没有BYTE为单位的移位运算,不知道是为什么。
PSLL[W,D,Q] |
逻辑左移[word,dword,qword] |
PSRL[W,D,Q] |
逻辑右移[word,dword,qword] |
PSRA[W,D,Q] |
算术右移[word,dword,qword] |
再提一句,这条指令的源操作数(就是放在后面的那个)可以是个立即数,就像 PSLLW MM0, 2 这样,就是每个WORD左移2位。还有,当源操作数是MMX寄存器或是内存地址时,CPU是把它当成一个数来看的,注意了,不要以为还是当分开的数据看,对应的单元移位对应的位数(我曾经以为会是这样,但事实证明我是错的),那有这种美事?
好了,今天就讲这么多吧!还有最后一类指令,留到下次吧!
补充日期: 2000-11-17 17:21:19
差点儿误人子弟,修正一下
PANDN指令的操作是:将目的数取非再和源数做与操作,用C语言表示为
dest = (~dest) & src
对不起,这条指令我原来没用过,想当然了,抱歉抱歉。
| | |
|
8.数据转换指令 现在已经讲了绝大部分的MMX指令,大家看到了吧,每个指令前都有个“P”开头。这个“P”的意思就是——Packet,就是说每个寄存器里的数据都是个数据包,而不是一个数据。那么这些指令就是把这些数据打包或拆包(我不知该怎么下这个操作的定义)用的。这些指令的操作都有点儿怪异,倒需要详细说明一下。
PACKUSWB |
有符号数WORD带饱和压缩成无符号BYTE |
PACKSS[WB,DW] |
有符号数带饱和压缩成有符号数[word->byte,dword->word] |
PUNPCKH[BW,WD,DQ] |
交错放置两数的高位[byte->word,word->dword,dword->qword] |
PUNPCKL[BW,WD,DQ] |
交错放置两数的低位[byte->word,word->dword,dword->qword] |
先说一下PACKUSWB。举个例子,PACKUSWB MM0, MM1,其实际操作为(图示为高位在左,低位在右), ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐
MM1│ A │ B │ C │ D │ │ E │ F │ G │ H │MM0
└───┴───┴───┴───┘ └───┴───┴───┴───┘
│ │
└─────────────┐ ┌─────────────┘
│ │
┌─┬─┬─┬─┬─┬─┬─┬─┐
│A │B │C │D │E │F │G │H │MM0
└─┴─┴─┴─┴─┴─┴─┴─┘ 不知道大家有没有看明白。大致意思就是:目的数紧缩到低位,源操作数紧缩到高位。另外,这个指令是带饱和操作的,就是说大于255的紧缩后就变成255,小于0的紧缩后就变成0。 PACKSS[WB,DW]的操作和PACKUSWB一样,不过多了个[DW],就是从DWORD紧缩成WORD。
再说一下拆包操作。名字虽然是UNPACK,实际好像不是PACK的逆操作,反倒有点怪,这个操作和乘法操作有一点点类似,也有高位和低位之分的。比如说:PUNPCKHBW MM0, MM1,其实际操作为: ┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐
MM1│A1│B1│C1│D1│E1│F1│G1│H1│ │A2│B2│C2│D2│E2│F2│G2│H2│MM0
└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘
│ │
└──────────────┐ ┌──────────────┘
│ │
┌─┬─┬─┬─┬─┬─┬─┬─┐
│A1│A2│B1│B2│C1│C2│D1│D2│MM0
└─┴─┴─┴─┴─┴─┴─┴─┘ 有了PACKUSWB作基础,大家应该看懂这个图示了吧。这明明不是什么拆包操作是吧!唉,我不过是为了讲究名称的匹配,再说它的指令助记符也写着是UNPACK嘛,不就是个名称,你又何必认真呢?PUNPCKL[BW,WD,DQ]的操作和PUNPCKH类似,就是把两个数的低位交错着放到目的数里。
OK,到此为止,MMX指令集简介正式结束,谢谢大家。
| | | | |