2012-01-24 16 views
8

生のピクセルデータを処理するコードを最適化しようとしています。現在、コードのC++実装は遅すぎるため、MSVC 2008でSSEイントリンシック(SSE/2/3では4を使用しない)を使用していくつかの根拠を作っています。これは初めてのことですが、いくつか良い進歩を遂げました。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組み込み関数がちょっと難しいと感じています。

どのような提案やヒントも高く評価されます。

EDIT:一度に単一のピクセルを処理 同等のC++コードは次のようになり:16:3:

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から非線形エッジストレッチをやっている9。

+2

SSEの組み込み関数が読みにくい。その時から、私は解決策を最適化するために、SSEのベクトル化を使用することができました。このセクションを説明するためにいくつかのコメント/等価な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]); 
} 

を使用すると、シリアルメモリアクセスの性能向上を失うが、剰余演算と2ドロップ2つのループに分割することにより、基本的にはこの

// 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] 
} 

と構文的に同等です条件付き演算子。

ここで、ループごとに2つの論理演算子があることに気が付いただけでなく、追加するかもしれない乗算は、SSE組み込み実装で​​はないです。あなたのvn1.m123i_u16 []アレイには何が保存されていますか?それは唯一のゼロと1つですか? もしそうなら、あなたはこの部分を必要とせず、それをなくすことができます。そうでない場合は、この配列のデータをゼロと1だけに正規化できますか? vn1.m123i_u16配列のみとゼロが含まれている場合、このコードは

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

なりまた、私はisEvenFloor * vnPx... partを実行するためにも、iIsEvenFlooriIsOddFloorレジスタを保存するためにSSEの乗算を使用していないわかります。すみませんが、u16乗算/レジスタのSSE組み込み関数が上から外れているのを覚えていませんが、このアプローチが役立つことを願っています。あなたが掲載しました、そして私の変更、我々はまだSSE1/2/3の組み込み関数を駆使していないコードのこのセクションで

// 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] 

が、それはいくつかのポイントを提供するかもしれない:一部の最適化は、あなたがになっているはずです(コードをどのようにベクトル化するのか)について説明します。

最後に、私はすべてをテストすると言います。上記のコードを変更せずに実行し、変更とプロファイリングを再度行う前にプロファイリングしてください。実際のパフォーマンスの数字はあなたを驚かせるかもしれません!


アップデート1

私はこれのために有用であることができ、関連する組み込み関数を選び出すためにIntel SIMD Intrinsics documentationを進めてきました。具体的にはビット単位のXORを見て、AND及びMULTは/

__m128データ型
を追加__m128iデータ型は16個の8ビット、8つの16ビット、4つの32ビット、あるいは2つの64を保持することができますビットの整数値。

__m128i _mm_add_epi16(__ m128iのA、__m128i b)は
__ m128iのA(__m128i _mm_mulhi_epu16 Bに8符号付きまたは符号なし16ビット整数に

を8つの符号付きまたは符号なし16ビット整数を追加、__m128i b)
aの8つの符号なし16ビット整数にbの8符号なし16ビット整数を乗算します。 は、8の符号なし32ビット

R0 = HIWORD(A0の*のB0)
R1 = HIWORD(A1の*のB1)
R2 = HIWORD(A2の*のB2)
結果の上位16ビットをパックR3 = HIWORD(A3の*のB3)
..
R7 = HIWORD(A7の*のB7)

__m128i _mm_mullo_epi16(__ m128iのA、__m128i b)は
乗算8符号付きまたは符号なし16ビット整数からaは8-signedまたはunsigned 16- bからの8ビット整数。 は、8符号付きまたは符号なし32ビット

R0 = LOWORD(A0の*のB0)
R1 = LOWORD(A1の*のB1)
R2 = LOWORD(A2の* B2の)結果の上位16ビットをパック
R3 = LOWORD(A3の*のB3)
..
R7 = LOWORD(A7の*のB7)

__m128i _mm_and_si128(__ m128iのA、__m128i b)は
はビット単位を実行し、128ビットのm1の値とm2の128ビットの値。

__m128i _mm_andnot_si128(__ m128i aは、__m128i b)は
はビット単位を計算し、Bに128ビット値とNOT IN 128 ビット値のビット単位の。

__m128i _mm_xor_si128(__ m128iのA、__m128i b)は
は、M2における128ビット値M1に128ビット値のビットごとの排他的論理和を実行します。参照のためのあなたのコードの例からALSO

uint16のU1 = U2 = U3 ...= u15 = 0x1
__m128i vnMask = _mm_set1_epi16(0x0001); // 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ループに分割しました。 私は比較を行うために乗算/加算の使用を考慮していませんでした。私が大切にしているのは、2つのforループを1つの命令セットに結合することです。 – ZeroDefect

+0

@zrxdはい私はそれを実現しました。編集しました。見てケア? –

2

Andrewあなたの提案は、最適なソリューションに近づいてくれます。真理値表とカルノーマップの組み合わせを使用して

、私はコード

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

は!XOR(排他的論理和ではない)機能であることに煮詰めことを発見しました。すべての助けを

//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バージョンとvanilla C++コードのようなパフォーマンスはどうですか? 「真理表とカルノーマップ」それは好きです。私はGCSEエレクトロニクスのためにそれらをやって覚えている! –

+1

ありがとうございます。 SSEの実装は半分以下で実行されます。もともと(この記事を投稿する前に)、組み立てられたC++の実装を見て、いくつかの提案を共有したいと考えました。残念ながら、C++の実装は、すべての分岐(およびキャッシュミス)によって厳しく制限されています。!xorパターンを最大限に活用することはできません。そして、はい、カルノーマップは爆弾です。 – ZeroDefect

+0

素晴らしい!あなたはu16を使用していますが、キャプチャするには最大理論速度8倍の改善があります。おそらくあなたが見つけたように、より簡単に言われました! –

関連する問題