2012-01-24 35 views
8

我一直在試圖優化一些處理原始像素數據的代碼。目前代碼的C++實現過於緩慢,所以我一直試圖使用SSE intrinsics(SSE/2/3不使用4)和MSVC 2008做一些理由。考慮到這是我第一次在這麼低的水平上進行挖掘,取得了一些進展。SSE內在函數 - 比較if/else優化

不幸的是,我已經到一個特定的代碼塊已經卡住我:

//Begin bad/suboptimal SSE code 
__m128i vnMask = _mm_set1_epi16(0x0001); 
__m128i vn1  = _mm_and_si128(vnFloors, vnMask); 

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++) 
{ 
    bool bIsEvenFloor = vn1.m128i_u16[m]==0; 

    vnPxChroma.m128i_u16[m] = 
     m%2==0 
      ? 
     (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m]) 
      : 
     (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
} 

目前,我默認爲使用C++實現本節,因爲我不能很讓我的頭瞭解如何使用SSE優化這一點 - 我發現SSE內在函數比較有點棘手。

任何建議/提示將不勝感激。

編輯:即一次處理一個像素 等效C++代碼將是:

short pxCl=0, pxFl=0; 
short uv=0; // chroma component of pixel 
short y=0; // luma component of pixel 

for(int i = 0; i < end-of-line, ++i) 
{ 
    //Initialize pxCl, and pxFL 
    //... 

    bool bIsEvenI  = (i%2)==0; 
    bool bIsEvenFloor = (m_pnDistancesFloor[i] % 2)==0; 

    uv = bIsEvenI ==0 
     ? 
    (bIsEvenFloor ? pxCl : pxFl) 
     : 
    (bIsEvenFloor ? pxFl : pxCl); 

    //Merge the Y/UV of the pixel; 
    //... 
} 

基本上,我正在做一個非線性邊緣伸展從4:3 16:9。

+2

上證所內部函數是難以閱讀。你介意添加一些評論/等價的C++代碼塊來解釋這一部分? –

+0

你想要代碼做什麼? – ronag

+0

我有點被這個片段(神祕的標識和沒有上下文)困惑,但你爲什麼不換成乘法和加法比較? – zrxq

回答

7

好的,所以我不知道這段代碼在做什麼,但是我確實知道你在問如何優化ternery運算符並讓這部分代碼只在SSE中運行。作爲第一步,我會建議嘗試使用整數標誌和乘法來避免條件運算符。例如:

本節

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++) 
{ 
    bool bIsEvenFloor = vn1.m128i_u16[m]==0;  

    vnPxChroma.m128i_u16[m] = m%2==0 ? 
     (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m]) : 
     (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
} 

在語法上等同於該

// DISCLAIMER: Untested both in compilation and execution 

// Process all m%2=0 in steps of 2 
for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2) 
{ 
    // This line could surely pack muliple u16s into one SSE2 register 
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0 
    uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

    // This line could surely perform an SSE2 multiply across multiple registers 
    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
           iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 
} 

// Process all m%2!=0 in steps of 2 
for(int m=1; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2) 
{ 
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0 
    uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxFloorChroma.m128i_u16[m] + 
           iIsOddFloor * vnPxCeilChroma.m128i_u16[m] 
} 

基本上被分裂成兩個循環你失去的串行存儲器訪問的性能提升,但下降模運算,並且兩個條件操作符。

現在你說,你注意到每個循環有兩個布爾運算符,以及我可能添加的乘積不是SSE內部實現。什麼存儲在你的vn1.m123i_u16 []數組中?它只是零和那些? 如果是這樣,你不需要這部分,可以取消它。如果沒有,你能否規範化你的數據在這個數組中只有零和一個?如果vn1.m123i_u16陣列只包含一和零那麼這個代碼變得

uint16 iIsOddFloor = vn1.m128i_u16[m] 
uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

您還會注意到我沒有使用SSE乘以執行isEvenFloor * vnPx... part也不存儲iIsEvenFlooriIsOddFloor寄存器。很抱歉,我不記得U16的SSE內部函數是從頂部乘以/註冊的,但我希望這種方法很有幫助。一些優化技術你應該看看:

// This line could surely pack muliple u16s into one SSE2 register 
uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0 
uint16 iIsEvenFloor = iIsOddFloor^0x1 // Flip 1 to 0, 0 to 1 

// This line could surely perform an SSE2 multiply across multiple registers 
vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
          iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 

在這部分代碼你張貼,我的修改,我們還沒有充分利用SSE1/2/3的內部函數,但它可能會提供一些點如何做到這一點(如何矢量化代碼)。

最後我會說要測試一切。在進行更改和分析之前,先運行上面的代碼並對其進行配置。實際的表現數字可能讓你感到驚訝!


更新1

我已經通過Intel SIMD Intrinsics documentation挑選出相關的內部函數這可能是使用了這一點。具體地來看看按位異或,AND和MULT/ADD

__m128數據類型
的__m128i數據類型可容納16個8位,8個16位,4個32位,或2個64位整數值。

__m128i _mm_add_epi16(__ m128i一個,__m128i B)
在添加8個符號或無符號16位整數有符號的8或16位無符號整數b中

__m128i _mm_mulhi_epu16(__ m128i一個,__m128i b)
將a的8個無符號16位整數乘以b的8個無符號16位整數。 包8無符號的32位的高16位結果

R0 = HIWORD(A0 * B0)
R1 = HIWORD(A1 * B1)
R2 = HIWORD(A2 * B2)
R3 = HIWORD(A3 * B3)
..
R7 = HIWORD(A7 * B7)

__m128i _mm_mullo_epi16(__ m128i一個,__m128i b)
倍增來自8符號或無符號16位整數一個由8位有符號或無符號的16位數字組成,來自b的位整數。 包的高位16比特的8符號或無符號的32位結果

R0 = LOWORD(A0 * B0)
R1 = LOWORD(A1 * B1)
R2 = LOWORD(A2 * b2)的
R3 = LOWORD(A3 * B3)
..
R7 = LOWORD(A7 * B7)

__m128i _mm_and_si128(__ m128i一個,__m128i b)
執行按位與128位的m1中的值與以m2計的128位值。

__m128i _mm_andnot_si128(__ m128i一個,__m128i B)
計算的位與B中的128位的值和所述按比特的NOT在所述128 比特值。

__m128i _mm_xor_si128(__ m128i一個,__m128i B)
在M1執行按位異或128位值的與平方米的128位的值。

從您的代碼示例

也可對照

UINT16 U1 = U2 = U3 ...= U15 =爲0x1
__m128i vnMask = _mm_set1_epi16(0×0001); //設置8個有符號的16位整數值。

UINT16 VN1 [I] = vnFloors [I] &爲0x1
__m128i VN1 = _mm_and_si128(vnFloors,vnMask); //計算a中128位值與b中128位值之間的按位與。

+0

是否可以使用按位AND而不是乘法? – zrxq

+0

謝謝,我已經打破了C++實現成兩(2)分離的for循環如你所說。 用乘法我沒有考慮到/加做比較。我所熱衷的部分是將兩個for循環合併爲一個(1)指令集。 – ZeroDefect

+0

@zrxd是的,我剛剛意識到,編輯。小心看一下? –

2

安德魯您的建議使我沿着小路到接近最優解。

使用真值表和卡諾圖的組合,我發現代碼

uv = bIsEvenI ==0 
    ? 
(bIsEvenFloor ? pxCl : pxFl) 
    : 
(bIsEvenFloor ? pxFl : pxCl); 

歸結爲是一個!XOR(異或不)函數。從那時起,我就能夠使用矢量SSE優化的解決方案:

//Use the mask with bit AND to check if even/odd 
__m128i vnMask    = _mm_set1_epi16(0x0001); 

//Set the bit to '1' if EVEN, else '0' 
__m128i vnFloorsEven  = _mm_andnot_si128(vnFloors, vnMask); 
__m128i vnMEven    = _mm_set_epi16 
    (
     0, //m==7 
     1, 
     0, 
     1, 
     0, 
     1, 
     0, //m==1 
     1 //m==0 
    ); 


// Bit XOR the 'floor' values and 'm' 
__m128i vnFloorsXorM  = _mm_xor_si128(vnFloorsEven, vnMEven); 

// Now perform our bit NOT 
__m128i vnNotFloorsXorM  = _mm_andnot_si128(vnFloorsXorM, vnMask); 

// This is the C++ ternary replacement - using multipilaction 
__m128i vnA     = _mm_mullo_epi16(vnNotFloorsXorM, vnPxFloorChroma); 
__m128i vnB     = _mm_mullo_epi16(vnFloorsXorM, vnPxCeilChroma); 

// Set our pixels - voila! 
vnPxChroma     = _mm_add_epi16(vnA, vnB); 

感謝所有幫助...

+0

哇!幹得好,發佈解決方案的好工作!出於興趣,SSE版本與香草C++代碼的性能如何? '真理表和卡諾圖'就像它。我記得爲GCSE電子產品做這些事情! –

+1

謝謝。 SSE實施運行時間不到一半。最初(在發佈這篇文章之前),我看了一下組裝的C++實現,希望它能分享一些建議。不幸的是,C++實現受到所有分支(和緩存未命中)的嚴重阻礙 - 它並沒有充分利用!xor模式。是的,卡諾圖是炸彈。 – ZeroDefect

+0

太棒了!雖然c你使用的是u16,但最大理論速度提升爲8x。說起來容易做起來難,你可能已經發現了! –