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
技术内反而会降低速度。(这个时候,如果真的用查表有提升速度的话,建议采用段地址+偏移量的办法)