8

私は生のピクセルデータを処理するいくつかのコードを最適化しようとしています。現在、コードのC ++実装は遅すぎるので、MSVC 2008でSSE組み込み関数(SSE / 2/3は4を使用しない)を使用していくつかの根拠を作ろうとしています。いくつかの良い進歩を遂げました。

残念ながら、私は立ち往生している特定のコードに到達しました:

//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組み込み関数は少し注意が必要です。

任意の提案/ヒントをいただければ幸いです。

編集: 一度に1つのピクセルを処理する同等の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までの非線形エッジストレッチを実行しています。

4

2 に答える 2

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

基本的に、2つのループに分割すると、シリアルメモリアクセスのパフォーマンスの向上は失われますが、モジュロ演算と2つの条件演算子は削除されます。

ここで、ループごとに2つのブール演算子があり、追加する可能性のある乗算はSSE固有の実装ではないことに気づきました。vn1.m123i_u16 []配列には何が格納されていますか?ゼロと1だけですか?もしそうなら、あなたはこの部分を必要とせず、それをなくすことができます。そうでない場合は、この配列のデータを0と1のみを持つように正規化できますか?vn1.m123i_u16配列に1と0のみが含まれている場合、このコードは次のようになります。

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

また、SSE乗算を使用して、およびレジスタを実行しisEvenFloor * vnPx... partたり、格納したりしていないことに気付くでしょう。申し訳ありませんが、u16のSSE組み込み関数を上から乗算/登録することを思い出せませんが、それでもこのアプローチが役立つことを願っています。あなたが調べるべきいくつかの最適化:iIsEvenFlooriIsOddFloor

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

私はIntelSIMD組み込み関数のドキュメントを調べて、これに役立つ可能性のある関連する組み込み関数を選び出しました。具体的には、ビット単位のXOR、AND、およびMULT/ADDを見てください。

__m128データ型
__m128iデータ型は、16個の8ビット、8個の16ビット、4個の32ビット、または2個の64ビット整数値を保持できます。

__m128i _mm_add_epi16(__ m128i a、__m128i b)a
の8つの符号付きまたは符号なし16ビット整数をbの8つの符号付きまたは符号なし16ビット整数に追加します

__m128i _mm_mulhi_epu16(__ m128i a、__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 a、__m128i b)a
の8つの符号付きまたは符号なし16ビット整数にbの8つの符号付きまたは符号なし16ビット整数を乗算します。8符号付きまたは符号なし32ビットの結果の上位16ビットをパックします

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 a、__m128i b)
m1の128ビット値とm2の128ビット値のビットごとのANDを実行します。

__m128i _mm_andnot_si128(__ m128i a、__m128i b)b
の128ビット値のビット単位のANDとaの128ビット値のビット単位のNOTを計算します。

__m128i _mm_xor_si128(__ m128i a、__m128i b)
m1の128ビット値とm2の128ビット値のビット単位のXORを実行します。

また、参照用のコード例から

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ビット値のビットごとのANDを計算します。

于 2012-01-24T12:29:17.240 に答える
2

アンドリューの提案は、私をほぼ最適な解決策へと導きます。

真理値表とカルノー図の組み合わせを使用して、私はコードが

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

要約すると、!xor(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);

すべての助けをありがとう...

于 2012-01-25T10:50:04.103 に答える