要素単位

要素単位#

問題

データセット内の項目全体で同様の独立した計算を開始し、すべてが完了するまで待機します。

コンテキスト

多くのシリアル・アルゴリズムは、一連の項目をスキャンし、各項目に対して独立した計算を実行します。ただし、何らかのサマリー情報が収集される場合は、代わりにリダクション・パターンを使用します。

強制

計算間で情報は伝達または結合されません。

解決方法

項目数が事前に判明している場合、oneapi::tbb::parallel_for を使用します。そうでない場合は、oneapi::tbb::parallel_for_each の使用を検討してください。

個々の計算がスケジューラーのオーバーヘッドに比べて小さい場合は、凝集を使用します。

パターンの後に同じデータのリダクションが続く場合、リダクションの一部として要素ごとの操作を実行することを検討してください。これにより、2 つのパターンの組み合わせが 2 回ではなく 1 回の操作で実行されます。これにより、メモリー階層を通過するトラフィックが削減され、パフォーマンスが向上する可能性があります。

畳み込みは信号処理でよく使用されます。フィルタ c と信号 x の畳み込みは次のように計算されます。

image0 この計算のシリアルコードは次のようになります。

// c[0..clen-1] と x[1-clen..xlen-1] が定義されていると仮定 
for( int i=0; i<xlen+clen-1; ++i ) { 
    float tmp = 0; 
    for( int j=0; j<clen; ++j ) 
        tmp += c[j]*x[i-j]; 
    y[i] = tmp; 
}

簡単にするために、ここでは、x がゼロで埋められた配列へのポインターであり、k<0 または k≥xlen の場合に x[k] がゼロを返すことを想定しています。

それぞれ反復は前の反復に依存するため、内側のループは要素ごとのパターンに適合しません。ただし、外側のループは要素ごとのパターンに適合します。次のように、oneapi::tbb::parallel_for を使用してレンダリングするのは簡単です。

oneapi::tbb::parallel_for( 0, xlen+clen-1, [=]( int i ) { 
    float tmp = 0; 
    for( int j=0; j<clen; ++j ) 
        tmp += c[j]*x[i-j]; 
    y[i] = tmp; 
});

oneapi::tbb::parallel_for は、基礎となる実装で oneapi::tbb::auto_partitioner を暗黙的に使用して自動集約を行います。明示的に集約する理由がある場合は、明示的なレンジ引数を受け取る oneapi::tbb::parallel_for のオーバーロードを使用します。以下は、オーバーロードを使用するように変換された例を示します。

oneapi::tbb::parallel_for( 
    oneapi::tbb::blocked_range<int>(0,xlen+clen-1,1000), 
    [=]( oneapi::tbb::blocked_range<int> r ) { 
            int end = r.end(); 
        for( int i=r.begin(); i!=end; ++i ) { 
            float tmp = 0; 
            for( int j=0; j<clen; ++j ) 
                tmp += c[j]*x[i-j]; 
            y[i] = tmp; 
        } 
    } 
);