4

AVX2 を使用して、視差推定アルゴリズムの「勝者総取り」部分を最適化しています。私のスカラー ルーチンは正確ですが、QVGA 解像度と 48 視差では、私のラップトップでは実行時間が 14 ミリ秒と残念なほど遅くなります。LR 視差画像と RL 視差画像の両方を作成しますが、簡単にするために、ここでは RL 検索のコードのみを含めます。

私のスカラールーチン:

int MAXCOST = 32000;
for (int i = maskRadius; i < rstep-maskRadius; i++) {

    // WTA "RL" Search:
    for (int j = maskRadius; j+maskRadius < cstep; j++) {
        int minCost = MAXCOST;
        int minDisp = 0;
        for (int d = 0; d < numDisp && j+d < cstep; d++) {
            if (asPtr[(i*numDisp*cstep)+(d*cstep)+j] < minCost) {
                minCost = asPtr[(i*numDisp*cstep)+(d*cstep)+j];
                minDisp = d;
            }
        }
        dRPtr[(i*cstep)+j] = minDisp;
    }
}

AVX2 を使用する私の試み:

int MAXCOST = 32000;
int* dispVals = (int*) _mm_malloc( sizeof(int32_t)*16, 32 );

for (int i = maskRadius; i < rstep-maskRadius; i++) {

    // WTA "RL" Search AVX2:
    for( int j = 0; j < cstep-16; j+=16) {

        __m256i minCosts = _mm256_set1_epi16( MAXCOST );
        __m128i loMask   = _mm_setzero_si128();
        __m128i hiMask   = _mm_setzero_si128();

        for (int d = 0; d < numDisp && j+d < cstep; d++) {
            // Grab 16 costs to compare
            __m256i costs = _mm256_loadu_si256((__m256i*) (asPtr[(i*numDisp*cstep)+(d*cstep)+j]));

            // Get the new minimums
            __m256i newMinCosts = _mm256_min_epu16( minCosts, costs );

            // Compare new mins to old to build mask to store minDisps
            __m256i mask   = _mm256_cmpgt_epi16( minCosts, newMinCosts );
            __m128i loMask = _mm256_extracti128_si256( mask, 0 );
            __m128i hiMask = _mm256_extracti128_si256( mask, 1 );
            // Sign extend to 32bits
            __m256i loMask32 = _mm256_cvtepi16_epi32( loMask );
            __m256i hiMask32 = _mm256_cvtepi16_epi32( hiMask );

            __m256i currentDisp = _mm256_set1_epi32( d );
            // store min disps with mask
            _mm256_maskstore_epi32( dispVals, loMask32, currentDisp );    // RT error, why?
            _mm256_maskstore_epi32( dispVals+8, hiMask32, currentDisp );  // RT error, why?

            // Set minCosts to newMinCosts
            minCosts = newMinCosts;
        }

        // Write the WTA minimums one-by-one to the RL disparity image
        int index = (i*cstep)+j;
        for( int k = 0; k < 16; k++ ) {
            dRPtr[index+k] = dispVals[k];
        }
    }
}
_mm_free( dispVals );

Disparity Space Image (DSI) のサイズは HxWxD (320x240x48) で、各行のサイズが WxD になるように、メモリ アクセスを改善するために水平に配置します。

Disparity Space Image には、ピクセルごとのマッチング コストがあります。これを単純なボックス フィルターで集約して、まったく同じサイズの別の画像を作成しましたが、合計コストは、たとえば 3x3 または 5x5 ウィンドウでした。この平滑化により、結果がより「ロバスト」になります。asPtr でアクセスしているときは、この集計コスト イメージにインデックスを付けています。

また、不要な計算を節約するために、マスク半径によってオフセットされた行で開始および終了しています。このマスク半径は、私の国勢調査マスクの半径です。派手な境界反射を行うこともできますが、この境界の不一致を気にしない方が簡単で高速です。もちろん、これは最初と最後の列にも当てはまりますが、アルゴリズム全体を列が 16 の倍数 (例: QVGA: 320x240) である画像に対してのみ実行するように強制している場合、ここでインデックスをいじるのは良くありません。単純にインデックスを作成し、すべてを SIMD でヒットします (残差スカラー処理なし)。

また、私のコードがごちゃごちゃしていると思われる場合は、高度に最適化された OpenCV ステレオ アルゴリズムを確認することをお勧めします。私はそれらを不可能だと思っており、それらをほとんどまたはまったく使用することができませんでした.

コードはコンパイルされますが、実行時に失敗します。VS 2012 Express Update 4 を使用しています。デバッガーで実行すると、洞察を得ることができません。私は組み込み関数の使用に比較的慣れていないため、デバッグ時にどのような情報が表示されるか、レジスターの数、__m256i 変数を表示する必要があるかどうかなど、よくわかりません。

以下のコメントのアドバイスに注意して、よりスマートなインデックス作成を使用して、スカラー時間を ~14 から ~8 に改善しました。私の CPU は i7-4980HQ で、同じファイルの別の場所で AVX2 組み込み関数を正常に使用しています。

情報画像

4

2 に答える 2

2

問題はまだ見つかっていませんが、変更が必要な点はいくつかありました。ただし、の戻り値をチェックしていません_mm_malloc。それが失敗している場合、それはそれを説明するでしょう。(もしかしたら、32 バイトにアラインされたメモリーを割り当てるのが好きではないのでしょうか?)

メモリ チェッカーなどでコードを実行している場合、初期化されていないメモリからdispVals. (_mm256_maskstore_epi32マスクがすべて 1 の場合でも、読み取り-変更-書き込みとしてカウントされる場合があります。)

デバッガーでコードを実行し、何が問題なのかを調べます。「実行時エラー」はあまり意味がありません。

_mm_set1*関数は遅いです。 VPBROADCASTDGP reg ではなくメモリまたはベクトル reg 内のソースを必要とするため、コンパイラはmovdGP reg からベクトル reg に変換してからブロードキャストするか、メモリに保存してからブロードキャストすることができます。とにかくやった方が早い

const __m256i add1 = _mm256_set1_epi32( 1 );
__m256i dvec = _mm256_setzero_si256();
for (d;d...;d++) {
    dvec = _mm256_add_epi32(dvec, add1);
}

その他:
内側のループのすべての反復をメモリに格納しない場合、これはおそらくより高速に実行されます。ブレンド命令 ( _mm256_blendv_epi8) などを使用して、最小コストに対応する変位のベクトルを更新します。ブレンド = 登録先のあるマスクされた移動。

また、変位値は 16b の整数に収まる必要があるため、それらを見つけ終わるまで 32b に符号拡張しないでください。Intel CPU は、16b メモリ ロケーションをオンザフライで gp レジスタに符号拡張できmovszますmovdRPtr配列を として宣言するだけuint16_tです。そうすれば、ベクトルコードに符号拡張要素はまったく必要ありません (内部ループは言うまでもありません!)。必要な 128 は既にlow128_mm256_extracti128_si256( mask, 0 )であるため、何もコンパイルされないことを願っていますvmovsx

最初にロードしないことで、命令 (および融合ドメイン uop) を保存することもできます。(たとえロード組み込み関数を使用したとしても、メモリ オペランドでvmovdquandを省略しないほどコンパイラが賢くない場合を除きます)。vpminuw

だから私はこのようなことを考えています:

// totally untested, didn't even check that this compiles.
for(i) { for(j) {
// inner loop, compiler can hoist these constants.
const __m256i add1 = _mm256_set1_epi16( 1 );
__m256i dvec = _mm256_setzero_si256();
__m256i minCosts = _mm256_set1_epi16( MAXCOST );
__m256i minDisps = _mm256_setzero_si256();

for (int d=0 ; d < numDisp && j+d < cstep ;
     d++, dvec = _mm256_add_epi16(dvec, add1))
{
    __m256i newMinCosts = _mm256_min_epu16( minCosts, asPtr[(i*numDisp*cstep)+(d*cstep)+j]) );
    __m256i mask   = _mm256_cmpgt_epi16( minCosts, newMinCosts );
    minDisps = _mm256_blendv_epi8(minDisps, dvec, mask); // 2 uops, latency=2
    minCosts = newMinCosts;
}

// put sign extension here if making dRPtr uint16_t isn't an option.
int index = (i*cstep)+j;
_mm256_storeu_si256 (dRPtr + index, __m256i minDisps);
}}

minCosts0/minDisps0minCosts1/の2 つの並列依存関係チェーンminDisps1を使用し、最後にそれらを結合すると、パフォーマンスが向上する場合があります。 minDispsはループ運搬の依存関係ですが、ループには 5 つの命令しかありません (vpaddループ オーバーヘッドのように見えますが、展開によって削減できない を含む)。これらは 6 uops (blendv は 2) にデコードされ、ループ オーバーヘッドが加算されます。haswell では 1.5 サイクル/反復 (ループ オーバーヘッドはカウントしない) で実行する必要がありますが、dep チェーンでは 2 サイクルごとに 1 反復に制限されます。(ループのオーバーヘッドを取り除くためにアンロールを想定しています)。2 つの dep チェーンを並行して実行すると、これが修正され、ループをアンロールするのと同じ効果があります。つまり、ループ オーバーヘッドが少なくなります。

うーん、実際にはハスウェルで、

  • pminuwp1/p5 で実行できます。(および p2/p3 のロード部分)
  • pcmpgtwp1/p5で実行可能
  • vpblendvbp5 では 2 uops です。
  • padduwp1/p5で実行可能
  • movdqa reg,regp0/p1/p5 で実行できます (実行ユニットはまったく必要ない場合もあります)。minCosts = newMinCostsコンパイラはnewMinCosts、次の反復の最初のループ本体の右側のレジスタに最後に展開されたループ本体から終了する可能性があるため、展開によって のオーバーヘッドを取り除く必要があります。
  • fuse sub/ jge(ループカウンター) は p6 で実行できます。( dvec でPTEST+を使用すると遅くなります)。/と融合していない場合、p0/p1/p5/p6 で実行できます。jccaddsubjcc

わかりました。実際には、ループは反復ごとに 2.5 サイクルかかり、p1/p5 でのみ実行できる命令によって制限されます。2 または 4 でアンロールすると、ループ/movdqaオーバーヘッドが削減されます。Haswell は 1 クロックあたり 4 つの uop を発行できるため、ループの反復回数が非常に多くないため、アウトオブオーダー実行のために uop をより効率的にキューに入れることができます。(48 はあなたの例です。) たくさんの uops をキューに入れると、ループを抜けた後に CPU に何かをさせることができ、キャッシュ ミスなどによる待ち時間を隠すことができます。

_mm256_min_epu16( PMINUW) は、別のループ運搬依存関係チェーンです。これをメモリ オペランドと共に使用すると、3 または 4 サイクルのレイテンシになります。ただし、命令のロード部分は、アドレスが判明するとすぐに開始できるため、ロードを変更操作に折りたたんでマイクロフュージョンを利用しても、個別のロードを使用するよりも dep チェーンが長くなったり短くなったりすることはありません。

場合によっては、アラインされていないデータに対して別のロードを使用する必要があります (AVX では、メモリ オペランドのアラインメント要件が削除されました)。4 uop/クロック発行の制限よりも実行ユニットによって制限されているため、専用のロード命令を使用しても問題ないでしょう。

insn ポート/レイテンシーのソース

于 2015-06-08T05:56:06.620 に答える