#ifdef ENABLE_SSE
#include
#endif
#ifdef ENABLE_SSE2
#include
#endif
ARM 浮点体系结构 (VFP) 为半精度、单精度和双精度浮点运算中的浮点操作提供硬件支持。它完全符合 IEEE 754 标准,并提供完全软件库支持。
ARM VFP 的浮点功能为汽车动力系统、车身控制应用和图像应用(如打印中的缩放、转换和字体生成以及图形中的 3D 转换、FFT 和过滤)中使用的浮点运算提供增强的性能。下一代消费类产品(如 Internet 设备、机顶盒和家庭网关)可直接从 ARM VFP 受益。
ARM® NEON™ 通用 SIMD 引擎可有效处理当前和将来的多媒体格式,从而改善用户体验。
NEON 技术可加速多媒体和信号处理算法(如视频编码/解码、2D/3D 图形、游戏、音频和语音处理、图像处理技术、电话和声音合成),其性能至少为 ARMv5 性能的 3 倍,为 ARMv6 SIMD 性能的 2 倍。
NEON 技术是通过干净方式构建的,并可无缝用于其本身的独立管道和寄存器文件。
NEON 技术是 ARM Cortex™-A 系列处理器的 128 位 SIMD(单指令,多数据)体系结构扩展,旨在为消费性多媒体应用程序提供灵活、强大的加速功能,从而显著改善用户体验。它具有 32 个寄存器,64 位宽(是 16 个寄存器,128 位宽的双倍视图。)
NEON 指令可执行“打包的 SIMD”处理:
使用 NEON 技术的 ARM Cortex™-A 系列处理器,以及 ARM 的 Mali 多媒体硬件解决方案可用于多媒体应用程序,范围从智能手机和移动计算设备到 HDTV。
X86的SIMD指令 ...simd instrucitons in X86
IA-32 Intel体系结构的指令主要分为以下几类 [1]:
MMX/SSE类扩展引入了SIMD(单指令多数据)的执行模式,可用于加速多媒体应用。 下面简要介绍一下这些指令的执行环境和特征。
MMX技术出现最早,目前几乎所有的X86处理器都提供支持,包括嵌入式X86, 所以下面的讨论主要基于MMX,但方法完全适用于SSEn, 包括像AMD的3D Now等其它SIMD扩展。
MMX指令又分为以下几种:
这些指令除了需要注意功能外,还需要注意处理的数据类型。以上内容为背景介绍,细节请参考手册。
回页首
性能优化 ...Performance Optimization
当使用C/C++完成了一个嵌入式应用的所有功能,性能问题常摆在面前, 这时可以使用profile工具(如gprof)找出产生瓶颈的函数, 将这些函数使用汇编彻底重写, 例如MPEG-4编解码器xvid项目 [4]就使用了这种方法, 而且针对不同处理器/指令集分别给出了不同的优化, 正是如此该项目无论功能、还是性能均为一流, 显然这是深度优化的目标所在。
在使用流水线、VLIW以及SIMD的体系结构(比如某些DSP)上, 整个函数的手工优化可以带来几倍到几十倍的性能提升。 不过,性能允许,对于函数内关键部分使用一些特定的实现, 既突出重点提高性能,又可以尽多地利用C/C++的高级特征, 相对缩短开发周期。 下面给出使用GCC时,应用MMX指令的几种混合编程方法:
回页首
Intel C/C++ 编译器intrinsics ...Intel C/C++ Compiler Intrinsics
查看IA-32 Intel指令集手册 [2]时, 部分指令的解释中会有一项“Intel C/C++ Compiler Intrinsic Equivalent”, 会指出该指令对等的intrinsic。 intrinsic在C/C++程序中的语法是以函数形式出现, 编译时可以直接翻译为一条MMX指令(复合情况会生成最直接的几条), 换言之,如果不使用intrinsic,可能需要多条C/C++语句完成, 而编译器却并不能保证将这几条语句能够生成这条最高效的MMX指令。 并不是每条MMX指令都有对等的intrinsic, 手册的附录中列出了所有的, 它们分为简单型(simple)和复合型(composite)两种, 每个简单型的就是对应一条指令,而复合型则对应多条指令。
GCC支持Intel C/C++ Compiler Intrinsics。用法如下示例:
#include #include /*一定需要包括此头文件*/ /*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"
回页首
使用built-in操作 ...GCC built-in Operation
什么是built-in操作?就是对待MMX操作数,就如int, float等基本数据类型一般, 有相应定义的操作,如加(+)、减(-),或者数据类型之间的转换。 详细内容参考GNU GCC Manual [5] Extensions to the C Language Family4#4Built-in Functions4#4 X86 Built-in Functions一节。
一些MMX指令有其相应的built-in操作, 下面一段代码为例:
#include /*无需特别的头文件,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); }
几点说明:
回页首
嵌入汇编 ...Inline asm
GCC一开始就允许C代码中嵌入asm指令,并不只是针对MMX指令, 不过对于MMX技术,显然也是一个很好的利用方法, 详细的语法请参考GNU GCC手册 [5], 或者GCC: The Complete Reference [6]''Inline Assembly''一节。 如下是一个点积的例子:
#include /** 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); }
几点说明:
回页首
MMX实用一例:合成滤波器 ...Synthesis Filter in X86 SIMD INSTRUCTIONS
下面是合成滤波器(Synthesis Filter)的一个优化过程, 合成滤波器在语音编解码中有广泛应用, 运行时也占用了整个算法中较高比例的时间。
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); }
几点说明:
回页首
总结 ...Conclusion
如果愿意尽多地利用SIMD技术,可能需要更多地使用汇编级的编码, 不过也有一些高级语言和汇编的混合编程技术能够帮助你, 它们有的提高性能更大一些, 有的形式上更优雅些,本质上效率也不错, 都不失好的方法,建议尝试。
正是如此,一方面CPU上支持越来越多的SIMD指令集扩展, 另一方面GCC也正在加紧支持这些扩展的易用,对,正在, 碰到一些问题,先想办法绕过去, 这里使用GCC 3.4.1,根据经验效果还是不错的。
回页首
关于文档
GCC中SIMD指令的应用方法
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 ®, 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-13
参考资料
关于作者
钱浙滨,1999年从上海交通大学图像处理与模式识别研究所获得博士学位, 曾参与完成计算机视觉、正规语言和移动通信等方面的研发工作; 目前他和他的团队主要从事DSP系统开发,特别是多媒体编解码算法的性能优化, 以及相关的Linux嵌入式应用; 他们也提供WLAN相关的技术咨询, 欢迎访问 http://embeddedcore.com进行交流。