GCC中SIMD指令的应用方法
X86的SIMD指令 ...simd instrucitons in X86
IA-32 Intel體系結(jié)構(gòu)的指令主要分為以下幾類 [1]:
- 通用
- x87 FPU
- MMX技術(shù)
- SSE/SSE2/SSE3擴展
MMX/SSE類擴展引入了SIMD(單指令多數(shù)據(jù))的執(zhí)行模式,可用于加速多媒體應(yīng)用。 下面簡要介紹一下這些指令的執(zhí)行環(huán)境和特征。
- 8個32位通用寄存器可為各個SIMD擴展所使用;
- MMX:8個64位MMX寄存器(mm0 - mm7),也可為各SSE擴展所使用;
- 數(shù)據(jù)為整數(shù),最多支持兩個32位
- 運算中沒有寄存器能夠進行溢出指示
- SSE:8個128位xmm寄存器,MXSCR寄存器,EFLAGS寄存器
- 支持單精度浮點
- MXSCR含有rounding, overflow標志
- 支持64位SIMD整數(shù)
- SSE2:執(zhí)行環(huán)境同sse
- 雙精度浮點
- 128位整數(shù)
- 雙—單精度轉(zhuǎn)換
- SSE3:與Inte Prescott處理器一同發(fā)布不久,共13條指令
- 主要增強了視頻解碼、3D圖形優(yōu)化和超線程性能
MMX技術(shù)出現(xiàn)最早,目前幾乎所有的X86處理器都提供支持,包括嵌入式X86, 所以下面的討論主要基于MMX,但方法完全適用于SSEn, 包括像AMD的3D Now等其它SIMD擴展。
MMX指令又分為以下幾種:
- 數(shù)據(jù)傳送:movd, movq
- 數(shù)據(jù)轉(zhuǎn)換:packsswb, packssdw, packuswb, punpckhbw, punpckhwd, punpckhdq, punpcklbw, punpcklwd, punpckldq
- 并行算術(shù):paddb, paddw, paddd, paddsb, paddsw, paddusb, paddusw, psubb, psubw, psubd, psubsb, psubsw, psubusb, psubusb, psubusw, pmulhw, pmullw, pmaddwd
- 并行比較:pcmpeqb, pcmpeqw, pcmpeqd, pcmpgtb, pcmpgtw, pcmpgtd
- 并行邏輯:pand, pandn, por, pxor
- 移位與旋轉(zhuǎn):psllw, pslld, psllq, psrlw, psrld, psrlq, psraw, psrad
- 狀態(tài)管理:emms
這些指令除了需要注意功能外,還需要注意處理的數(shù)據(jù)類型。以上內(nèi)容為背景介紹,細節(jié)請參考手冊。
性能優(yōu)化 ...Performance Optimization
當使用C/C++完成了一個嵌入式應(yīng)用的所有功能,性能問題常擺在面前, 這時可以使用profile工具(如gprof)找出產(chǎn)生瓶頸的函數(shù), 將這些函數(shù)使用匯編徹底重寫, 例如MPEG-4編解碼器xvid項目 [4]就使用了這種方法, 而且針對不同處理器/指令集分別給出了不同的優(yōu)化, 正是如此該項目無論功能、還是性能均為一流, 顯然這是深度優(yōu)化的目標所在。
在使用流水線、VLIW以及SIMD的體系結(jié)構(gòu)(比如某些DSP)上, 整個函數(shù)的手工優(yōu)化可以帶來幾倍到幾十倍的性能提升。 不過,性能允許,對于函數(shù)內(nèi)關(guān)鍵部分使用一些特定的實現(xiàn), 既突出重點提高性能,又可以盡多地利用C/C++的高級特征, 相對縮短開發(fā)周期。 下面給出使用GCC時,應(yīng)用MMX指令的幾種混合編程方法:
- Intel C/C++ 編譯器intrinsics
- GCC builtin操作
- 嵌入?yún)R編asm construct
Intel C/C++ 編譯器intrinsics ...Intel C/C++ Compiler Intrinsics
查看IA-32 Intel指令集手冊 [2]時, 部分指令的解釋中會有一項“Intel C/C++ Compiler Intrinsic Equivalent”, 會指出該指令對等的intrinsic。 intrinsic在C/C++程序中的語法是以函數(shù)形式出現(xiàn), 編譯時可以直接翻譯為一條MMX指令(復(fù)合情況會生成最直接的幾條), 換言之,如果不使用intrinsic,可能需要多條C/C++語句完成, 而編譯器卻并不能保證將這幾條語句能夠生成這條最高效的MMX指令。 并不是每條MMX指令都有對等的intrinsic, 手冊的附錄中列出了所有的, 它們分為簡單型(simple)和復(fù)合型(composite)兩種, 每個簡單型的就是對應(yīng)一條指令,而復(fù)合型則對應(yīng)多條指令。
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位整數(shù)的mm寄存器 */__m64 m2; /* MMX操作需要使用mm寄存器 */__m128 m128; /* for SSEn only*//*每次往mm寄存器裝入兩個short型的數(shù),注意是兩個*/m1 = _mm_cvtsi32_si64(((int*)in1)[0]);m2 = _mm_cvtsi32_si64(((int*)in2)[0]); /*一條指令進行4個16位整數(shù)的乘加*//*生成兩個32位整數(shù)*/m2 = _mm_madd_pi16(m1, m2); /*將低32位整數(shù)放入通用寄存器*/out1 = _mm_cvtsi64_si32(m2);/*將高32位整數(shù)右移后,放入通用寄存器*/m2 = _mm_slli_pi32(m2, 32);out2 = _mm_cvtsi64_si32(m2);/*清除MMX狀態(tài)*/_mm_empty();/*將兩個32位數(shù)相加,結(jié)果為8*/out1 += out2;printf("a: %d/n", out1);return(0);} |
幾點說明:
- 即使你不是P4平臺,編譯時也請使用以下選項,
/*gcc -Wall -march=pentium4 -mmmx -o ins mmx_ins.c*/
否則,會出現(xiàn)如下類似信息:
...xmmintrin.h:34:3: #error "SSE instruction set not enabled" - 最終結(jié)果實際并沒有求得四對乘積的和,只是前兩對的, instrinsic _mm_cvtsi32_si64只向mm寄存器放入了低32位,高32位為零, 但mmx有指令movq可以做到64位的數(shù)據(jù)傳送,intrinsic沒有對應(yīng), 這也說明并不是所有的指令有等價的intrinsic。
- 當計算的向量為兩對0x8000, 0x8000時,即(-2^15)*(-2^15) + (-2^15)*(-2^15) , 結(jié)果應(yīng)該為 2^31,但計算出來的值是 -2^31, 因為發(fā)生了溢出,可程序無從知道。 這是使用MMX時,應(yīng)特別注意的,計算溢出沒有任何標志位指示,一個極大的值變?yōu)闃O小,SSE對此做了改善。
- 程序不再使用MMX之時,注意使用emms指令清除MMX狀態(tài)。
使用built-in操作 ...GCC built-in Operation
什么是built-in操作?就是對待MMX操作數(shù),就如int, float等基本數(shù)據(jù)類型一般, 有相應(yīng)定義的操作,如加(+)、減(-),或者數(shù)據(jù)類型之間的轉(zhuǎn)換。 詳細內(nèi)容參考GNU GCC Manual[5] Extensions to the C Language Family4#4Built-in Functions4#4 X86 Built-in Functions一節(jié)。
一些MMX指令有其相應(yīng)的built-in操作, 下面一段代碼為例:
| #include <stdio.h>/*無需特別的頭文件,built-in嘛*//* gcc -Wall -o bins builtinmmx.c*//*定義了一個vector數(shù)據(jù)類型,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的輸出還需要強制轉(zhuǎn)換為long long*/printf("...with MMX instructions...to compute vec_add: %llx /n", (long long)vsum);//結(jié)果1:0xfffd0000ffff8000//結(jié)果2:0xfffd0000ffff0000return(0);} |
幾點說明:
- 是的,這里built-in vector及其操作,隨著GCC的發(fā)展正在加強。如果需要使用以上范例,應(yīng)使用GCC 3.4以上版本;
- 使用builtin函數(shù)時,與intrinsic相似;但本質(zhì)卻是不同,這里兩個向量使用‘+’操作就說明了vector也如其它數(shù)據(jù)類型一樣,編譯器直接支持,只不過這里的加法就是指四個單元數(shù)分別相加,低位單元的進位不會影響相鄰高位單元的數(shù)據(jù);
- vector還可以強制轉(zhuǎn)換為通用數(shù)據(jù)。
嵌入?yún)R編 ...Inline asm
GCC一開始就允許C代碼中嵌入asm指令,并不只是針對MMX指令, 不過對于MMX技術(shù),顯然也是一個很好的利用方法, 詳細的語法請參考GNU GCC手冊 [5], 或者GCC: The Complete Reference [6]''Inline Assembly''一節(jié)。 如下是一個點積的例子:
| #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,每四對數(shù)相乘后分兩組相加,形成兩組和*//*這里的循環(huán)控制是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);//這里結(jié)果為0x24return(0);} |
幾點說明:
- 這里是典型的在函數(shù)中C和匯編混合編程;
- 注意匯編指令中操作數(shù)的順序;
- 這里可以直接使用movq等沒有intrinsics/built-in對應(yīng)的指令;
- 注意在asm指令序列中間不要加雜注釋,可能導(dǎo)致生成的代碼不正確。
MMX實用一例:合成濾波器 ...Synthesis Filter in X86 SIMD INSTRUCTIONS
下面是合成濾波器(Synthesis Filter)的一個優(yōu)化過程, 合成濾波器在語音編解碼中有廣泛應(yīng)用, 運行時也占用了整個算法中較高比例的時間。
| 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 |
上面的代碼,因為內(nèi)存循環(huán)為10,可以考慮展開,并統(tǒng)一操作為乘加指令。
| /*為了使用乘加操作,需要調(diào)整10個系數(shù)的順序*/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);} |
以上循環(huán)內(nèi)核正好可以將MMX的8個寄存器全部利用。
| /*為了使用乘加操作,需要調(diào)整10個系數(shù)的順序*/for(i = 0; i < M; i++)ta[i] = -a[M - i];ta[11] = 0;ta[10] = a[0];/*11個系數(shù)分別放入3個MMX寄存器,0作填充*/asm("movq %0,%%mm0;/movq %1,%%mm1;/movq %2,%%mm2"/:/: "m" (ta[0]), "m" (ta[4]), "m"(ta[8]));/*利用MMX技術(shù)進行濾波器核心操作*/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]));/*因為指令結(jié)果飽和屬性的限制,s還沒有左移,所以下面多做一位飽和左移*/s = L_shl(s, 4);*yy++ = g729round(s);} |
幾點說明:
- 注意:以上嵌入的匯編代碼輸出結(jié)果s放在了輸入處,屬于實踐中的個案;
- MMX沒有乘左移之類的DSP指令,甚至還沒有加飽和之類的操作,SSE中有一定增強;
- 以上操作,理論上存在溢出可能,所以最后使用原有的飽和左移操作,減少了一定風險;
- 上面的部分代碼操作顯然允許并行,這在VLIW系統(tǒng)中十分有用;
- 這已經(jīng)形成了該濾波器全面優(yōu)化的核心。
總結(jié) ...Conclusion
如果愿意盡多地利用SIMD技術(shù),可能需要更多地使用匯編級的編碼, 不過也有一些高級語言和匯編的混合編程技術(shù)能夠幫助你, 它們有的提高性能更大一些, 有的形式上更優(yōu)雅些,本質(zhì)上效率也不錯, 都不失好的方法,建議嘗試。
正是如此,一方面CPU上支持越來越多的SIMD指令集擴展, 另一方面GCC也正在加緊支持這些擴展的易用,對,正在, 碰到一些問題,先想辦法繞過去, 這里使用GCC 3.4.1,根據(jù)經(jīng)驗效果還是不錯的。
關(guān)于文檔
GCC中SIMD指令的應(yīng)用方法
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
參考資料
關(guān)于作者
| ? | 錢浙濱,1999年從上海交通大學圖像處理與模式識別研究所獲得博士學位, 曾參與完成計算機視覺、正規(guī)語言和移動通信等方面的研發(fā)工作; 目前他和他的團隊主要從事DSP系統(tǒng)開發(fā),特別是多媒體編解碼算法的性能優(yōu)化, 以及相關(guān)的Linux嵌入式應(yīng)用; 他們也提供WLAN相關(guān)的技術(shù)咨詢, 歡迎訪問http://embeddedcore.com進行交流。 | |
總結(jié)
以上是生活随笔為你收集整理的GCC中SIMD指令的应用方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VS2010/MFC编程入门之四(MFC
- 下一篇: 关于H264通过RTP传输的打包方式