1. 程式人生 > >SIMD在影象處理中的應用及彙編優化

SIMD在影象處理中的應用及彙編優化

SIMD(Single Instruction Multiple Data),顧名思義,就是單條指令處理多個數據。比如我們處理BYTE型別的乘法,也是需要32位暫存器來做處理.這樣顯得有些浪費.實際上我們可以把乘數合併,一次32位的乘法得到兩個乘法結果.示例程式碼如下:

R1 = a1*b;

R2 = a2*b;

轉換成

T1 = (a1 | (a2 <<16))*b;

R1 = T1&0xffff;

R2 = T1>>16;

在遊戲/多媒體等典型的圖形處理應用中,我們經常需要對連續密集的資料進行處理.這正是SIMD長處.本文重點討論利用SIMD思想對

16色做alpha混合的處理以及相應的彙編優化.

.簡單應用

16色顯示,由於人眼對綠色相對敏感,一般顯示卡對R5G6B5支援的更好.這時alpha值只需要分32級即可滿足要求.alpha值取[0,32].所以通用的混合邏輯大概如下:

u16bppalpha16bpp(constu16bppsrcColu16bppdesColu8bppalpha)

{

      u16bppb=(GET_BLUE(srcCol)*alpha + GET_BLUE(desCol)*(32-alpha))/32;

      u16bppg = (GET_GREEN(srcCol)*alpha

 + GET_GREEN(desCol)*(32-alpha))/32;

      u16bppr = (GET_RED(srcCol)*alpha + GET_RED(desCol)*(32-alpha))/32;

      returnRGB_16(r,g,b);

}

仔細觀察顏色的bit分佈,我們可以發現,如果把G位移到高位,再在G位前後空出5bit出來,正好可以一次乘法可以得到三個顏色的*alpha.最終我們的處理邏輯大概如下:

       unsigneds1 = ((srcCol << 16) | srcCol)& 0x7e0f81f;

                     unsigned

d1 = ((desCol << 16) | desCol)& 0x7e0f81f;

                     unsignedr = (((s1-d1)*alpha)/32 + d1) & 0x7e0f81f;

       returnu16bpp((r >> 16) |(r & 0xffff));

經測試,這樣修改後.效率有成倍的提升.並且由於不涉及到彙編,具有非常好的移植性.

.指令級應用

由於SIMD能很大幅度的提升效能.各個廠商在設計CPU,一般都會內建指令來支援。比如x86MMX,SSE,AVX以及ARMNEON.這裡重點介紹P4引入的SSE2alpha混合做進一步的優化

要想利用新的SSE2 指令,有兩種方式:彙編和intrinsic函式形式的SSE.一般我們先採用函式形式對演算法進行改造,相對於彙編它更易於除錯,移植性/可讀性也更好.

SSE2支援xmm0-7 8128位的暫存器。演算法的SSE化,重點在於資料的讀取/儲存格式.

第一感覺我們想到一次讀取 8個顏色,按照順序處理前後4個顏色.但是在儲存處理過的資料時,由於PACKSSDW指令是飽和運算.對於大於0x7fff的值會截掉而導致問題.採用其他方式處理又會增加更多的指令換一種方式,把顏色分奇偶位分別處理,儲存時只需要做一次位移和OR操作即能得到結果.

實際演算法改造的過程中,我們發現SSE2並不支援每32位的乘法.只有PMULUDQ指令支援每次兩個32位整數相乘,我們可以把資料分解,分兩次計算在做OR操作.

這部分程式碼比較長,核心邏輯程式碼可以看附件測試程式碼.

四.彙編優化

        intrinsic函式SSE處理的效能,比較依賴編譯器的實現.在測試中,就發現VC9GCC4VC715%左右.要做進一步的優化,彙編級的處理是必須的

相對於編譯器的自動優化,彙編可以有針對的修改邏輯,充分地利用有限的幾個暫存器,減少記憶體讀寫次數.比如把for(int i=0; i<h; ++i) 改成 for(int i=h; i>0; --i)

此外,現代X86 CPU都具備亂序執行的特性,彙編可以更方便的調整指令的順序,減少相鄰指令的依賴關係,根據CPU的計算單元特性,混排指令.比如在兩個乘法指令間增加加減的指令.最終增加CPU執行的併發度以提高效能.

核心程式碼詳見附件.最終優化相對於原始的ALPHA混合函式,可以提升4倍以上.

五.32ALPHA混合

32ARGB格式的ALPHA混合也可以採用類似於16位的處理方式進行SIMD修改.為了減少混合運算的指令數目,要把ALPHA值從0-255範圍內擴充套件到0-256.編譯器一般會把乘除256轉換成位移操作.後續再用到SIMD運算時,這裡的擴充套件也會減少更多的運算.這部分的展開相比於原始的處理提升一倍以上.

程式碼邏輯大概如下:

staticinlineu32bppalphaMul32bpp(u32bppcu32bppscale)

        {

                     constu32bppmask = 0xFF00FF;

                     u32bpprb = ((c & mask) * scale) >> 8;

                     u32bppag = ((c >> 8) & mask) * scale

                     return (rb & mask) | (ag & (~mask));

        }

         unsignedalpha32bppOp(u32bppsrcu32bppdest ,u8bppalpha)

        {

                      u32bppnalpha = alpha+1;

                     u32bppdalpha = 256-nalpha;

                     returnalphaMul32bpp(srcnalpha) + alphaMul32bpp(destdalpha);

         }