MMX开发文档

MMX 开发文档
I MMX简介
       Intel MMX™ 技术是对 Intel 体系结构 (IA) 指令集的扩展。该技术使用了单指令多数据技术 (SIMD) 技术,以并行方式处理多个数据元素,从而提高了多媒体和通讯软件的运行速度。 MMX™ 指令集增加了 57 条新的操作码和一个新的 64 位四字数据类型。
MMX™ 技术提高了很多应用程序的执行性能,例如活动图像、视频会议、二维图形和三维图形。几乎每一个具有重复性和顺序性整数计算的应用程序都可以从 MMX™ 技术中受益。对于 8 位、 16 位和 32 位数据元素的处理,改善了程序的性能。一个 MMX™ 指令可一次操作 8 个字节,且在一个时钟周期内完成两条指令,也就是说,可在一个时钟周期内处理 16 个数据元素。另外,为增强性能, MMX™ 技术为其它功能释放了额外的处理器周期。以前需要其它硬件支持的应用程序,现在仅需软件就能运行。更小的处理器占用率给更高程度的并发技术提供了条件,在当今众多的操作系统中这些并发技术得到了利用。在基于 Intel 的分析系统中,某些功能的性能提高了 50% 400% 。这种数量级的性能扩展可以在新一代处理器中得到体现。在软件内核中,其速度得到更大的提高,其幅度为原有速度的三至五倍。
MMX 的缺点 :由于 MMX 的运算指令必须在数据配对整齐的时候才能使用,所以使用 MMX 指令要比普通的汇编指令多余许多分组配对的指令,如果运算不是特别的整齐的话,就要浪费大量的时间在数据的配对上,所以说 MMX 指令也不是万能的,也有其很大的缺陷。同时 MMX 指令在处理 16 位数据的时候才能发挥最大的作用,处理 8 位数据要有一点技巧。而处理 32 位数据, MMX 指令几乎没有什么加速能力。 ( 考虑分组耗时的话 )
II MMX基本指令集
       具体细节请参阅《 INTEL 体系结构 MMX 技术程序员参考手册》第五章            
2.1拷贝指令
       movq 64 位数据拷贝,如果内存 8 位对齐的话,是一个 64 位写,否则 2 32 位写。
       movd 32 位数据拷贝,注意:如果从内存向 MMX 寄存器拷贝, MMX 32 位清零!
2.2分组指令
       分组指令是 MMX 特有的,所以对于它我们要特别的关注。分组指令基本上可以分为 2 类,一类是不带符号紧缩的,一类是带符号紧缩的。现在我们分别予以介绍:
punpcklbw / punpcklwd / punpckldq (l 表示低位分组, bw8 位, wd16 位, dq32 ) :它是简单的将两个 MMX 寄存器的低 32 位交错组合为一个 64 位数据。所以它是不能将长数据转换为短数据的。
packuswb 16 位数据转换为无符号的 8 位数据。所以可以将两个 MMX 寄存器不交错的合为一个 64 位数据。
packsswb/packssdw 32 位-》 16 位, 16 位-》 8 位,都是有符号的数据。
2.3运算指令
       加法运算指令: paddb w )( d ):没有越界保护的加法,当越界的时候仅仅丢弃超出范围的高位比特,( b )( w )( d )分别为 8 16 32 位加法; paddsb w ):具有越界保护的有符号加法,当上溢的时候为 0x7fff ,下溢的时候为 0x8000 paddusb w ):具有越界保护的无符号加法,当上溢的时候为 0x7fff ,下溢的时候为 0x0
       减法运算指令同上; add 改为 sub
       乘法指令: pmullw  / pmulhw 4 16 位数据的乘法, pmullw 中是结果的低 16 位, pmulhw 是结果的高 16 位。 pmaddwd 乘加指令。
2.4逻辑指令,移位指令和EMMS指令
       细节参见《 INTEL 体系结构 MMX 技术程序员参考手册》。
III MMX经典处理策略
数据输入输出:
在输入数据的时候,经典的处理方法是将一个数组整个“ Load ”到 MMX 寄存器中。这样简单同时利用了 MMX64 位读写数据的能力,提高了性能。同样在输出的时候,也是将一个 64 MMX 寄存器中的数据内容整个“ Store ”到内存中。
如果实在是不能这样处理的话,就要利用移位指令了。比如说将一个 MMX 内的 4 16 位数据分别拷贝到不同的内存变量(或者 16 位通用寄存器中) x1 x2 x3 x4 ,那么可以这样处理:
movd     eax,mm1
psrlq       mm1,32
movd     ebx,mm1
mov       x1,ax
mov       x2,bx
shr          eax,16
shr          ebx,16
mov        x3,ax
mov        x4,bx
可见如果不采用数组形式的话,输入输出将十分的麻烦。
 
数据分组以及求绝对值的方法等:
    细节请参阅《 INTEL 体系结构MMX™ 技术开发者手册》第五章                     
IV 自定义组合指令
八位无符号数的移位:
       MMX 指令集中是没有 8 位数据的移位指令的,但是有的时候我们确实需要,所以可以用以下两个指令来实现:
       psrlq       mm0,1
       pand              mm0,0x7f7f7f7f7f7f7f7f
如何防止计算过程中越界:
       比如在计算的时候,我们有( x1 x2 1 >>1 ,这个时候 x1 x2 就会越界( 8 位数据),那么我们就不得不使用替代了办法,比如( x1>>1 x2>>1 )这个处理是不精确的,在不需要很精确的场合,是可以使用的,但是如果结果差错 1 都不可容忍的话,就要进行一点处理:
       pand              mm0,0x01010101010101             // 保留数据的最后一位数
       pand              mm1,0x01010101010101             // 保留数据的最后一位数
       por         mm0,mm1                                 
       paddusb mmx,mm0                                  // 修正数据
x1>>2 x2>>2 ):这个处理是通用的
       pand   mm0,0x03030303030303             //保留数据的最后两位数
pand              mm1, 0x03030303030303            //保留数据的最后两位数
       paddusb mm0,mm1
       psrlq       mm0,2
       pand              mm0,0x3f3f3f3f3f3f3f3f
       paddusb mmx,mm0
符号扩展指令:
       mm0 *,*,A,B =>  现在要符号扩展为 mm0 (A 符号 )A, (A 符号 )B
       movq      mm1,mm0     
pcgtm  mm1,0                         // 比较 mm0 ,生成 mm1 (A 符号 ) (B 符号 )()()
punpcklwd     mm0,mm1
分组指令
       除了基本的分组指令以外,我们还可以利用移位指令和 pand por 指令来实现分组的功能,移位主要是要产生 0 ,这样 por mm0 mm1 就可以将 mm0 mm1 合并了。
比如: mm0(*,*,A,B) mm1(0,0,C,D)  
       psllq        mm0,32
       por         mm0,mm1 => (A,B,C,D) 当然这个例子我们可以用普通的分组指令实现,但是在某些复杂的处理中,这样的处理是必须的。
总之,要灵活运用 MMX 的现有指令来实现自己需要的功能。
V MMX编程心得
       使用 MMX 技术进行编程,目的就是要提高运算速度,所以,对于如何尽可能的提高代码的效率,我们是要特别关注的。这里,我介绍一些需要注意的事项。
尽可能的提高内存访问的容量,我们可以看看下面的代码:
 for (j=0; j<h; j++)
 {
    d[0] = s[0];
    d[1] = s[1];
    d[2] = s[2];
    d[3] = s[3];
    d[4] = s[4];
    d[5] = s[5];
    d[6] = s[6];
    d[7] = s[7];
    d[8] = s[8];
    d[9] = s[9];
    d[10] = s[10];
    d[11] = s[11];
    d[12] = s[12];
    d[13] = s[13];
    d[14] = s[14];
    d[15] = s[15];
    s+= lx2;
    d+= lx;
 }
 
__asm{
              pushf
            movedx,dword ptr h
              xor ecx,ecx
              mov esi,dword ptr s
              movedi,dword ptr d
              moveax,lx2
              mov ebx,lx
AGAIN:
              movq      mm0,byte ptr [esi]
              movq      mm1,byte ptr [esi+8]
              movq      byte ptr [edi],mm0
              movq      byte ptr [edi+8],mm1
              add         esi,eax
              add         edi,ebx
              add         ecx,1
              cmp        ecx,edx
              jl             AGAIN
              emms
              popf
       }
 
仅仅将几个 8 位的写,改为 64 位的写,测试得到速度提升了 25% ,同样的道理,我们要尽可能的将几个 movq 写在一起,这样可以提高 5% 左右的速度。原 C 代码的效率也是很高的,它不用数组的【】【】来寻址,而是将 s+= lx2; 从而将二维数组的寻址改为一维数组的寻址。尽可能的减少寻址的复杂度,这也是一种高效的办法。 还有一点就是如果将原来的简单赋值改为 memcpy () 的话,可以提高大约 10 %的速度。这也是提高了数据流通容量的关系。
 
一些要注意的地方:
1.       尽可能的使用 static 变量 , 访问这样的变量是很快的 = 访问立即数的速度
2.       由于只有一个 mmx 移位寄存器 , ( 移位分组指令 ) 是不能配对的
3.       不要在 eax 使用完 , 使用 ax, 不要使用完一个 mm1, 就立即使用它
4.       可以这样立即使用 mm1, movq mm2,mm1 movq mm1,mm3 (Z 顺序是可以的 )
5.       (4 个以上 )movq 尽可能的在一起 , 前提是在一起的 mov 不要使用一样的 mmx 寄存器
6.       mov eax, [esi] ([esi+2*eax]) 访问寻址的内存 是特别的慢的
7.       同上 stow 也是很慢的 mov cx n;   loop 是很慢的,如果可能,要展开循环)
8.       尽可能的在寄存器中完成操作 , 不要去访问内存
9.       用变量名访问变量 , 尤其是 static , 是很快
10.      访问寻址的内存的速度下降 > 数据不对齐8位的速度下降 > 指令不配对的速度下降
11.    所以在传统的代码优化的方法中,构造数组,然后将运算变为查表的方法,有的时候在 MMX 技术内反而会降低速度。(这个时候,如果真的用查表有提升速度的话,建议采用段地址+偏移量的办法)

你可能感兴趣的:(MMX开发文档)