|
举例子是可以的,但就怕你看不懂。
举例详解
下面的程序是一个矩阵相乘的函数。在三维图形空间变换中,要用到4乘4的浮点矩阵,
而矩阵相乘的运算是很常用的。下面的函数的参数都是4乘4的浮点矩阵。写成这种形式
是为了保持比较强的伸缩性。
void MatMul_cpp(float *dest, float *m1, float *m2)
{
for(int i = 0; i < 4; i ++)
{
for(int j = 0; j < 4; j ++)
{
dest[i*4+j] =
m1[i*4+0]*m2[0*4+j] +
m1[i*4+1]*m2[1*4+j] +
m1[i*4+2]*m2[2*4+j] +
m1[i*4+3]*m2[3*4+j] ;
}
}
}
VC的优化能力是很强的,象上面这样的比较常规的算法,你很难做出比它快得多的代码
。不过使用SSE以后就不一样了。下面是一个汇编函数,使用SSE 指令进行计算。注意,这个函数只能运行于32位的环境中。“.xmm”指示编译器使用SSE指令集进行编译。
函数的C语言原型是这样的:
extern "C"
{
void __stdcall MatMul_xmm(float *dest, float *m1, float *m2);
}
对于一些不太常用汇编语言编程的朋友来说,下面的程序可能比较难于理解。我将对一些常识性的东西做一下简单介绍。
在C语言中,代码段都是以“_TEXT”作为段名的。“use32”告诉编译器将代码编译为32位。
有些人看到“_MatMul_xmm@12”这个函数名以后可能会产生疑问。其实这只是遵循了VC所采用的命名规范。在VC中,所有标志为“__stdcall” 调用的,采用“C”链接的函数都要加下划线作为前缀,并且加上“@N”作为后缀,其中,“N”为参数的字节数。注意,上面的函数是采用“C”链接的,如果是“C++”链接,命名规范就太复杂了。如果你使用的是C++ Builder,命名规范就十分简单了,照搬函数名就行了。不同的调用规范将采用不同的命名方法,即使对相同的调用规范,不同的编译器也不一定兼容。有一种调用格式是每一个C++编译器都支持并且兼容的,那就是“__cdecl”。
各种调用格式所采用的堆栈操作也不太一样。使用“__stdcall”时,参数从右向左依次入栈,参数的弹出需要函数自己来处理。这种做法和“__cdecl” 调用方式不太一样,“__cdecl”的参数弹出需要调用者来处理。现在很流行的一种调用格式是“__fastcall”,也就是寄存器调用。这种调用方式通过寄存器“EAX”,“ECX”,“EDX”传递参数,不过很可惜,这种调用也不是在各个编译器中兼容的。Inprise在C++ Builder中提供了一个关键字“__msfastcall” 用来和微软兼容,如果你采用这种调用规范就可以在多个编译器中正常调用了。不过还有一件事让人很受打击,VC没有对“__fastcall”提供很好的优化,使用这种调用反而会降低效率。并不是所有的寄存器都能够随意使用的,多数32位寄存器都要先保存的。你可以不必保存的32位寄存器只有三个----“EAX”,“ECX”,“EDX”,其它的就只好“PUSH”,“POP”了。另外,浮点堆栈寄存器是不必保存的;MMX 寄存器和浮点堆栈共享,也是不必保存的;XMM寄存器不必保存。
很多SSE指令都会加上“ps”或“ss”后缀。“ps”表示“Packed Single-FP”,即打包
的浮点数,带这种后缀的指令通常是一次性对四个数进行操作的。“ss” 表示“Scalar Single-FP”,带这种后缀的指令通常是对最低位的单精度数进行操作的。
下面这个汇编函数是一行一行计算的,咱们先用类似于C的语法简述一下第一行的计算过
程:
xmm0 = m1[0],m1[0],m1[0],m1[0];
xmm1 = m1[1],m1[1],m1[1],m1[1];
xmm2 = m1[2],m1[2],m1[2],m1[2];
xmm3 = m1[3],m1[3],m1[3],m1[3];
xmm4 = m2[0],m2[1],m2[2],m2[3];
xmm5 = m2[4],m2[5],m2[6],m2[7];
xmm6 = m2[8],m2[9],m2[10],m2[11];
xmm7 = m2[12],m2[13],m2[14],m2[15];
xmm0 *= xmm4;
xmm1 *= xmm5;
xmm2 *= xmm6;
xmm3 *= xmm7;
xmm1 += xmm0;
xmm2 += xmm1;
xmm3 += xmm2;
dst[0],dst[1],dst[2],dst[3] = xmm3;
上面的代码可读性还是比较好的,因为只进行了第一行的计算。实际运算中,为了增强并行度,为了减小指令的延迟,实际上是两行并行计算的。而且,运算过程并不是象算法描述那样写得那么有规律。
.686p
.xmm
.model flat
_TEXT segment public use32 'CODE'
public _MatMul_xmm@12
_MatMul_xmm@12 proc
;;parameters
retaddress = 0
dst = retaddress+4
m1 = dst+4
m2 = m1+4
mov edx, [esp+m1]
mov ecx, [esp+m2]
mov eax, [esp+dst]
movss xmm0, [edx+16*0+4*0] ;读入第一行的数据
movaps xmm4, [ecx+16*0]
movss xmm1, [edx+16*0+4*1]
shufps xmm0, xmm0, 00h
movaps xmm5, [ecx+16*1]
movss xmm2, [edx+16*0+4*2]
shufps xmm1, xmm1, 00h
mulps xmm0, xmm4
movaps xmm6, [ecx+16*2]
mulps xmm1, xmm5
movss xmm3, [edx+16*0+4*3]
shufps xmm2, xmm2, 00h
movaps xmm7, [ecx+16*3]
shufps xmm3, xmm3, 00h
mulps xmm2, xmm6
addps xmm1, xmm0
movss xmm0, [edx+16*1+4*0] ;读入第二行的数据
mulps xmm3, xmm7
shufps xmm0, xmm0, 00h
addps xmm2, xmm1
movss xmm1, [edx+16*1+4*1]
mulps xmm0, xmm4
shufps xmm1, xmm1, 00h
addps xmm3, xmm2
movss xmm2, [edx+16*1+4*2]
mulps xmm1, xmm5
shufps xmm2, xmm2, 00h
movaps [eax+16*0], xmm3
movss xmm3, [edx+16*1+4*3]
mulps xmm2, xmm6
shufps xmm3, xmm3, 00h
addps xmm1, xmm0
movss xmm0, [edx+16*2+4*0] ;读入第三行的数据
mulps xmm3, xmm7
shufps xmm0, xmm0, 00h
addps xmm2, xmm1
movss xmm1, [edx+16*2+4*1]
mulps xmm0, xmm4
shufps xmm1, xmm1, 00h
addps xmm3, xmm2
movss xmm2, [edx+16*2+4*2]
mulps xmm1, xmm5
shufps xmm2, xmm2, 00h
movaps [eax+16*1], xmm3
movss xmm3, [edx+16*2+4*3]
mulps xmm2, xmm6
shufps xmm3, xmm3, 00h
addps xmm1, xmm0
movss xmm0, [edx+16*3+4*0] ;读入第四行的数据
mulps xmm3, xmm7
shufps xmm0, xmm0, 00h
addps xmm2, xmm1
movss xmm1, [edx+16*3+4*1]
mulps xmm0, xmm4
shufps xmm1, xmm1, 00h
addps xmm3, xmm2
movss xmm2, [edx+16*3+4*2]
mulps xmm1, xmm5
shufps xmm2, xmm2, 00h
movaps [eax+16*2], xmm3
movss xmm3, [edx+16*3+4*3]
mulps xmm2, xmm6
shufps xmm3, xmm3, 00h
addps xmm1, xmm0
mulps xmm3, xmm7
addps xmm2, xmm1
addps xmm3, xmm2
movaps [eax+16*3], xmm3
ret 12
_MatMul_xmm@12 endp
_TEXT ends
end |
|