MMX和SSE的运用
源地址:http://falloutmx.blog.163.com/blog/static/3923602020101024914425/
? ? ? Intel的MMX技術是對Intel體系結構(IA)指令集的擴展。該技術使用了單指令多數據技術(SIMD)技術,以并行方式處理多個數據元素。MMX指令集增加了57條新的操作碼和一個新的64位四字數據類型,增加了八個新的64位MMX寄存器,每個寄存器可按名稱MM0-MM7直接訪問。這意味著我們可以在一個寄存器里最多存8個數據(最小單位字節)。?
 ? ? ? SSE系列是MMX的超集,直到SSE2才跟MMX有本質的區別,添加了對64位雙精度浮點數的支持,以及對整型數據的支持,也就是說這個指令集中所有的MMX指令都是多余的了,同時也避免了占用浮點數寄存器。這個指令集還增加了對CPU的緩存的控制指令。新的SSE寄存器命名為xmm0~xmm7,每個128位。?
 ? ? ? 對H264的優化主要用到了MMX和SSE2。舉個例子,在H264解碼端的IDCT和QUANT里,要操作的數是一個4X4的矩陣,很適合拿來優化。 IDCT計算公式:
 
?
其中,“Ei”已在前面的反量化中完成,其結果為?Wr,所以這里只進行以下運算:
? ? ? ?在反變換過程中,可以分成兩次一維變換,使用了蝶形算法來簡化運算。 原函數就不給了,直接看優化完成后的優化代碼:?memcpy(&m7,(&img->cof[i0][j0][0][0]),sizeof(short)*16); ? ? ? ?? //如果不能采用數組形式的話,輸入輸出將十分的麻煩。只能通過移位指令,浪費大量的時間。?
_mm_prefetch(&m7,_MM_HINT_NTA); ? ? ?//? _mm_prefetch將數據預存到cache里,一次存一個cache line的數據,_MM_HINT_NTA表示存到離CPU最近的cache里(既L1 cache)。在操作開始前執行_mm_prefetch可以避免由于讀未命中產生的大量延時。
?_asm{?
 movq mm0,[m7]// movq盡可能的在一起, 前提是在一起的mov不要使用一樣的mmx寄存器
?movq mm1,[m7+8]?//2.連續的讀指令可以配對,當指令可以配對的時候,一個時鐘周期可以同時處理這兩條指令(U管道與V管道)。最好避免AGI延遲
?movq mm2,[m7+16]
 ?movq mm3,[m7+24]?//通過MOVQ指令一次處理四個向量元素,并通過PADDW指令一次完成四個加法。將使速度提高四倍?
paddw mm2,mm0?????? ?//m6[0]=(m5[0]+m5[2]);每個指令前都有個“P”開頭。這個“P”的意思就是——Packet,就是說每個寄存器里的數據都是個數據包,而不是一個數據。指令最后的w表示按數據包里每個數據的大小是“word”, addw既paddw mm0,mm0?
psubw mm0,mm2 m6[1]=(m5[0]-m5[2]);
 movq mm4,mm1 psraw mm4, 1 (m5[1]>>1) //基本的優化常識,乘2和除2操作用移位代替。如果有必要使用常數,那么使用立即數通常比把常數讀取到寄存器更有效。但是如果同一個立即數被多次引用,應把常數先取到一個寄存器,這種方法更快?
 psubw mm4,mm3????????????????? // m6[2]=(m5[1]>>1)-m5[3];
 psraw mm3, 1?????????????????????? // (m5[3]>>1) //由于只有一個mmx移位寄存器, 移位分組指令是不能配對的?
 paddw mm1,mm3 ? ? ? ? ? ? ? // m6[3]=m5[1]+(m5[3]>>1);?
 paddw mm4,mm0 ? ? ? ? ? ? ? //? ?? img->m7[0][j]=m6[0]+m6[3];?
 paddw mm0,mm0?
 psubw mm0,mm4 ? ? ? ? ? ? ? //? ? ? ? ? img->m7[3][j]=m6[0]-m6[3];?
 paddw mm1,mm2 ? ? ? ? ? ?? // ? ? ? ?? img->m7[1][j]=m6[1]+m6[2];?
 paddw mm2,mm2?
 psubw mm2,mm1 ? ? ? ? ? ? ? //? ? ? ? ? img->m7[2][j]=m6[1]-m6[2];?
 //矩陣倒換,拆包和打包指令可以用于矩陣行列倒換?
 m5[j]=img->m7[i][j];
 
movq mm5,mm0
movq mm3,mm1
PUNPCKlwd mm1,mm4?
PUNPCKhwd mm3,mm4?
PUNPCKlwd mm0,mm2?
PUNPCKhwd mm5,mm2?
movq mm6,mm1?
movq mm7,mm3?
PUNPCKldq mm1,mm0?
PUNPCKhdq mm6,mm0
?PUNPCKldq mm3,mm5?
PUNPCKhdq mm7,mm5?
//第二次一維變換 ,跟第一次完全一樣?
movq mm4,mm6?
paddw mm3,mm1 ? ? ? ? ? ? // m6[0]=(m5[0]+m5[2]);?
paddw mm1,mm1?
psubw mm1,mm3 ? ? ? ? ? ? // m6[1]=(m5[0]-m5[2]);?
psraw mm4,1?
psubw mm4,mm7 ? ? ? ? ? ? // m6[2]=(m5[1]>>1)-m5[3];?
psraw mm7,1
paddw mm6,mm7 ? ? ? ? ? ? //m6[3]=m5[1]+(m5[3]>>1);?
paddw mm4,mm1 ? ? ? ? ? ? // img->m7[0][j]=m6[0]+m6[3];?
paddw mm1,mm1?
psubw mm1,mm4 ? ? ? ? ? ? // img->m7[3][j]=m6[0]-m6[3];?
paddw mm6,mm3 ? ? ? ? ? ? // img->m7[1][j]=m6[1]+m6[2];?
paddw mm3,mm3?
psubw mm3,mm6 ? ? ? ? ? ? // img->m7[2][j]=m6[1]-m6[2];
movq [m7+24],mm3 //在優化的時候,數據對齊是很關鍵的。比如說在這個地方,如果內存8位對齊的話,是一個64位寫,否則2個32位寫。
movq [m7+16],mm1?
 movq [m7+8],mm4?
 movq [m7],mm6?
EMMS // 由于MMX用的是浮點寄存器,使用EMMS指令清MMX寄存器并置浮點標志字的值為空(即為全1)。該指令應在代碼段結束時插入,以避免執行浮點代碼時產生浮點棧的溢出錯誤。這條指令將耗費20-50個時鐘周期。?
 }?
 
 
 像素恢復操作:
 ?img->m7[i][j] =max(0,min(img->max_imgpel_value,( img->m7[i][j] +((long)img->mpr[i+ioff][j+joff] <<6)+32)>>6));
 ?img->m7[i][j1]=max(0,min(img->max_imgpel_value,( img->m7[i][j1]+((long)img->mpr[i+ioff][j1+joff]<<6)+32)>>6));?
 MMX里沒有max和min相關的指令操作,因此用SSE2進行優化。Intel提供了intrinsics庫讓我們寫SSE代碼更方便快速,但是這樣會比匯編代碼要慢,因為它不在乎寄存器的分配和重新使用。
 舉個例子:?
 temp_m70_sse=_mm_slli_epi16(temp_m70_sse,6);?
 _asm{?
 movdqa xmm0,xmmword ptr [temp_m70_sse]?
 psllw xmm0,6?
 movdqa xmmword ptr [temp_m70_sse],xmm0?
 }?
 temp_m70_sse=_mm_add_epi16(temp_m70_sse,temp_32_sse);?
 _asm{?
 movdqa xmm0,xmmword ptr [temp_m70_sse]?
 movdqa xmm1,xmmword ptr [temp_32_sse]?
 paddw xmm0,xmm1?
 movdqa xmmword ptr [temp_m70_sse],xmm0?
 }?
 可以看到,運用這些指令的時候,每條指令都被單獨的處理,沒有考慮前后相關性,導致數據多次進行存取,降低了速度,有可能比原來的C語言更慢。 剩下沒什么好說的,優化后的代碼如下:
 ?temp_32_sse=_mm_set1_epi16(32);?
 m70_sse=_mm_set_epi16(i16a[13],i16a[9],i16a[5],i16a[1],i16a[12],i16a[8],i16a[4],i16a[0]); //由于要處理的數據在內存上不是連續的,因此把這些數據讀到xmm寄存器里會耗費大量時間,編譯后看反匯編的的代碼就知道了。 m71_sse=_mm_set_epi16(i16a[15],i16a[11],i16a[7],i16a[3],i16a[14],i16a[10],i16a[6],i16a[2]); temp_255_sse=_mm_set1_epi16(img->max_imgpel_value); temp_zero_sse=_mm_set1_epi16(0); temp_m70_sse=_mm_set_epi16(img->mpr[i1_ioff][3+joff],img->mpr[i1_ioff][2+joff],img->mpr[i1_ioff][1+joff],img->mpr[i1_ioff][joff], img->mpr[i0_ioff][3+joff],img->mpr[i0_ioff][2+joff],img->mpr[i0_ioff][1+joff],img->mpr[i0_ioff][joff]);?
 
 temp_m71_sse=_mm_set_epi16(img->mpr[i3_ioff][3+joff],img->mpr[i3_ioff][2+joff],img->mpr[i3_ioff][1+joff],img->mpr[i3_ioff][joff], img->mpr[i2_ioff][3+joff],img->mpr[i2_ioff][2+joff],img->mpr[i2_ioff][1+joff],img->mpr[i2_ioff][joff]);
 
 ?_asm{
 ?movdqa xmm0,xmmword ptr [temp_m70_sse]?
 movdqa xmm1,xmmword ptr [temp_32_sse]?
 movdqa xmm2,xmmword ptr [temp_m71_sse]?
 psllw xmm0,6?
 movdqa xmmword ptr [temp_m70_sse],xmm0
 ?paddw xmm0,xmm1?
 movdqa xmmword ptr [temp_m70_sse],xmm0
 ?psllw xmm2,6?
 paddw xmm2,xmm1?
 movdqa xmm1,xmmword ptr [m70_sse]?
 movdqa xmm3,xmmword ptr [m71_sse]?
 paddw xmm0,xmm1?
 paddw xmm2,xmm3?
 psraw xmm0,6?
 psraw xmm2,6?
 movdqa xmmword ptr [temp_m70_sse],xmm0?
 movdqa xmmword ptr [temp_m71_sse],xmm2?
 movdqa xmm1,xmmword ptr [temp_255_sse]?
 movdqa xmm3,xmmword ptr [temp_zero_sse]?
 pminsw xmm0,xmm1?
 pmaxsw xmm0,xmm3?
 pminsw xmm2,xmm1?
 pmaxsw xmm2,xmm3?
 packuswb xmm0,xmm2?
 movdqa xmmword ptr [temp_m70_sse],xmm0 //SSE不需要EMMS操作
 ?}?
 _mm_storeu_si128(img_m7,temp_m70_sse);//sse使用專用的結構_m128i,因此最后需要存到我們自己的數組里
 
總結
以上是生活随笔為你收集整理的MMX和SSE的运用的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 使用MMX/SSE汇编指令集优化视频开发
- 下一篇: 第41部分-Linux x86 64位汇
