Keywords: 优化,GCC, SIMD,MMX,SSE
MMX/SSE类扩展引入了SIMD(单指令多数据)的执行模式,可用于加速多媒体应用。 下面简要介绍一下这些指令的执行环境和特征。
MMX技术出现最早,目前几乎所有的X86处理器都提供支持,包括嵌入式X86, 所以下面的讨论主要基于MMX,但方法完全适用于SSEn, 包括像AMD的3D Now等其它SIMD扩展。
MMX指令又分为以下几种:
这些指令除了需要注意功能外,还需要注意处理的数据类型。以上内容为背景介绍,细节请参考手册。
当使用C/C++完成了一个嵌入式应用的所有功能,性能问题常摆在面前, 这时可以使用profile工具(如gprof)找出产生瓶颈的函数, 将这些函数使用汇编彻底重写, 例如MPEG-4编解码器xvid项目 [4]就使用了这种方法, 而且针对不同处理器/指令集分别给出了不同的优化, 正是如此该项目无论功能、还是性能均为一流, 显然这是深度优化的目标所在。
在使用流水线、VLIW以及SIMD的体系结构(比如某些DSP)上, 整个函数的手工优化可以带来几倍到几十倍的性能提升。 不过,性能允许,对于函数内关键部分使用一些特定的实现, 既突出重点提高性能,又可以尽多地利用C/C++的高级特征, 相对缩短开发周期。 下面给出使用GCC时,应用MMX指令的几种混合编程方法:
GCC支持Intel C/C++ Compiler Intrinsics。用法如下示例:
#include <stdio.h>
#include <xmmintrin.h> /*一定需要包括此头文件*/
/*gcc -Wall -march=pentium4 -mmmx -o ins mmx_ins.c*/
int main(int argc,char *argv[])
{
/*使用MMX做以下向量的点积*/
short in1[] = {1, 2, 3, 4};
short in2[] = {2, 3, 4, 5};
int out1;
int out2;
__m64 m1; /* MMX支持64位整数的mm寄存器 */
__m64 m2; /* MMX操作需要使用mm寄存器 */
__m128 m128; /* for SSEn only*/
/*每次往mm寄存器装入两个short型的数,注意是两个*/
m1 = _mm_cvtsi32_si64(((int*)in1)[0]);
m2 = _mm_cvtsi32_si64(((int*)in2)[0]);
/*一条指令进行4个16位整数的乘加*/
/*生成两个32位整数*/
m2 = _mm_madd_pi16(m1, m2);
/*将低32位整数放入通用寄存器*/
out1 = _mm_cvtsi64_si32(m2);
/*将高32位整数右移后,放入通用寄存器*/
m2 = _mm_slli_pi32(m2, 32);
out2 = _mm_cvtsi64_si32(m2);
/*清除MMX状态*/
_mm_empty();
/*将两个32位数相加,结果为8*/
out1 += out2;
printf("a: %d\n", out1);
return(0);
}
几点说明:
/*gcc -Wall -march=pentium4 -mmmx -o ins mmx_ins.c*/
否则,会出现如下类似信息:
...xmmintrin.h:34:3: #error "SSE instruction set not enabled"
一些MMX指令有其相应的built-in操作, 下面一段代码为例:
include <stdio.h>
/*无需特别的头文件,built-in嘛*/
/* gcc -Wall -o bins builtinmmx.c*/
/*定义了一个vector数据类型,hi表示16位,4表示4个*/
typedef int v4hi __attribute__ ((mode(V4HI)));
/*定义了2个32位的vector类型,si表示32位*/
typedef int v2si __attribute__ ((mode(V2SI)));
int main(int argc,char *argv[])
{
short pa[4] = {0x8000, 0x8000, 1, -1};
short pb[4] = {0x8000, 0x7FFF, -1, -2};
v4hi va, vb;
v4hi vsum;
va = ((v4hi*)pa)[0];
vb = ((v4hi*)pb)[0];
/* 4个16位进行饱和加 */
//vsum = __builtin_ia32_paddsw(va, vb);
/* 4个16位还可以直接进行加法,但不同于两个long long相加 */
vsum = va + vb;
/*vector的输出还需要强制转换为long long*/
printf("...with MMX instructions...to compute vec_add: %llx \n", (long long)vsum);
//结果1:0xfffd0000ffff8000
//结果2:0xfffd0000ffff0000
return(0);
}
几点说明:
如下是一个点积的例子:
#include <stdio.h>
/** GCC -o ins inlinemmx.c **/
int main(int argc,char *argv[])
{
int i;
int result;
short a[] = {1, 2, 3, 4, 5, 6, 7, 8};
short b[] = {1, 1, 1, 1, 1, 1, 1, 1};
printf("...with MMX instructions...\n");
/*首先,将点积合累积寄存器清零,实际缺省就为0?*/
asm("pandn %%mm5,%%mm5;"::);
/*读入a, b,每四对数相乘后分两组相加,形成两组和*/
/*这里的循环控制是C在做*/
for(i = 0; i < sizeof(a)/sizeof(short); i += 4){
asm("movq %0,%%mm0;\
movq %1,%%mm1;\
pmaddwd %%mm1,%%mm0;\
paddd %%mm0,%%mm5; #相乘后相加 "
:
: "m" (a[i]), "m" (b[i]));
}
/*将两组和分离,并相加*/
asm("movq %%mm5, %%mm0;\
psrlq $32,%%mm5;\
paddd %%mm0, %%mm5;\
movd %%mm5,%0;\
emms"
:"=r" (result)
:);
printf("result: 0x%x\n", result);
//这里结果为0x24
return(0);
}
几点说明:
for (i = 0; i < lg; i++)
{
s = L_mult(x[i], a[0]);/*L_mult是相乘后左移*/
for (j = 1; j <= M; j++){/*M这里固定为10*/
s = L_msu(s, a[j], yy[-j]);/*L_msu是乘减后左移操作*/
}
s = L_shl(s, 3); /*左移三位*/
*yy++ = g729round(s);
}
#endif
上面的代码,因为内存循环为10,可以考虑展开,并统一操作为乘加指令。
/*为了使用乘加操作,需要调整10个系数的顺序*/
for(i = 0; i < M; i++)
ta[i] = -a[M - i];
ta[11] = 0;
ta[10] = a[0];
for (i = 0; i < lg; i++){
*yy = x[i];
yy[1] = 0;
s = L_mac(s, ta[11], yy[1]);
s = L_mac(s, ta[10], yy[0]);
s = L_mac(s, ta[9], yy[-1]);
s = L_mac(s, ta[8], yy[-2]);
s = L_mac(s, ta[7], yy[-3]);
s = L_mac(s, ta[6], yy[-4]);
s = L_mac(s, ta[5], yy[-5]);
s = L_mac(s, ta[4], yy[-6]);
s = L_mac(s, ta[3], yy[-7]);
s = L_mac(s, ta[2], yy[-8]);
s = L_mac(s, ta[1], yy[-9]);
s = L_mac(s, ta[0], yy[-10]);
s = L_shl(s, 3);
*yy++ = g729round(s);
}
以上循环内核正好可以将MMX的8个寄存器全部利用。
/*为了使用乘加操作,需要调整10个系数的顺序*/
for(i = 0; i < M; i++)
ta[i] = -a[M - i];
ta[11] = 0;
ta[10] = a[0];
/*11个系数分别放入3个MMX寄存器,0作填充*/
asm("movq %0,%%mm0;\
movq %1,%%mm1;\
movq %2,%%mm2"\
:\
: "m" (ta[0]), "m" (ta[4]), "m"(ta[8]));
/*利用MMX技术进行滤波器核心操作*/
for (i = 0; i < lg; i++){
*yy = x[i];
yy[1] = 0;
asm("pandn %%mm6,%%mm6;\
movq %1,%%mm3;\
movq %2,%%mm4;\
movq %3,%%mm5;\
pmaddwd %%mm0,%%mm3;\
pmaddwd %%mm1,%%mm4;\
pmaddwd %%mm2,%%mm5;\
paddd %%mm3, %%mm6;\
paddd %%mm4, %%mm6;\
paddd %%mm5, %%mm6;\
movq %%mm6, %%mm7;\
psrlq $32, %%mm6;\
paddd %%mm7, %%mm6;\
movd %%mm6,%0;\
emms"
:
:"r"(s), "m" (yy[-10]), "m" (yy[-6]), "m"(yy[-2]));
/*因为指令结果饱和属性的限制,s还没有左移,所以下面多做一位饱和左移*/
s = L_shl(s, 4);
*yy++ = g729round(s);
}
几点说明:
正是如此,一方面CPU上支持越来越多的SIMD指令集扩展, 另一方面GCC也正在加紧支持这些扩展的易用,对,正在, 碰到一些问题,先想办法绕过去, 这里使用GCC 3.4.1,根据经验效果还是不错的。
This document was generated using the LaTeX2HTML translator Version 2002 (1.62)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -iso_language CN -html_version 4.0,unicode -address '©2004 CoreUp Designs' -local_icons -split 0 -nonavigation gccsimd
The translation was initiated by on 2004-12-27