インテル® TBB 2020 の有用な機能: タスクの分離

インテル® oneTBB

この記事は、インテル® デベロッパー・ゾーンに公開されている『Intel® Threading Building Blocks Documentation』の「Task Isolation」(https://software.intel.com/en-us/node/684814) の日本語参考訳です。


インテル® スレッディング・ビルディング・ブロック (インテル® TBB) では、タスクのグループの完了を待機するスレッドが他の利用可能なタスクを実行することがあります。特に、並列構造が別の並列構造を呼び出す場合、スレッドは内部レベルの完了を待機しつつ、外部レベルの構造からタスクを取得します。

次に 2 つの parallel_for 呼び出し例を示します。2 番目の (入れ子になった) 並列ループの呼び出しは、最初の (外部) ループの反復実行をブロックします。

// 最初の並列ループ
tbb::parallel_for( 0, N1, []( int i ) { 
    // 2 番目の並列ループ
    tbb::parallel_for( 0, N2, []( int j ) { /* 実際のワーク */ } );
} );

ブロックされたスレッドは、最初の並列ループに属するタスクを実行できます。その結果、外部ループの 2 つ以上の反復が同じスレッドに同時に割り当てられることがあります。インテル® TBB は、並列構造を構成する関数を単一スレッド内でも順番通りに実行しません。ほとんどの場合、この振る舞いはスレッドが実行可能な並列処理を制限することはないため、無害または有益です。

ただし、状況によっては、順番付けされない実行によりエラーが発生することがあります。例えば、スレッドローカル変数は、入れ子になった並列構造の処理後、予期しない値に変更される場合があります。

tbb::enumerable_thread_specific<int> ets;
tbb::parallel_for( 0, N1, [&ets]( int i ) {
    // スレッド固有の値を設定
    ets.local() = i;
    tbb::parallel_for( 0, N2, []( int j ) { /* Some work */ } );
    // 上記の parallel_for 実行中にスレッドは外部の parallel_for の反復を実行する可能性があり、
    // それによりスレッド固有の値が変更される可能性があります。
    assert( ets.local()==i ); // アサーション失敗!
} );

別のシナリオでは、この振る舞いはデッドロックやその他の問題を引き起こす可能性があります。この場合、スレッド内で順序付けして実行することが望まれます。そのため、インテル® TBB は並列構造の実行を「分離」する方法を提供します。これにより、タスクは、同時に実行される他のタスクに干渉しなくなります。

この方法の 1 つとして、個別の task_arena で内部レベルのループを実行する手法があります。

tbb::enumerable_thread_specific<int> ets;
tbb::task_arena nested;
tbb::parallel_for( 0, N1, [&]( int i ) {
    // スレッド固有の値を設定
    ets.local() = i;
    nested.execute( []{
        // 異なるアリーナで内部の parallel_for を実行して、
        // スレッドが外部の parallel_for タスクを取得しないようにする。
        tbb::parallel_for( 0, N2, []( int j ) { /* Some work */ } );
    } );
    assert( ets.local()==i ); // 有効なアサーション
} );

ただし、ワークの分離に個別のアリーナを使用することは必然ではなく、オーバーヘッドが生じる可能性があります。この問題に対処するため、インテル® TBB は、this_task_arena::isolate 関数を提供しています。この関数は、ファンクターのスコープでスケジュールされたタスクだけを処理するように呼び出しスレッドを制限することで、ユーザーが提供するファンクターを分離して実行します (分離領域とも呼ばれます)。

分離領域内でタスク待機呼び出しまたはブロック化された並列構造に入ると、スレッドはその領域内でスポーンされるタスクと他のタスクで生成された子タスクのみを実行します。スレッドは、外部レベルのタスクや他の分離領域に属するタスクの実行が禁止されます。

分離領域は、その領域を呼び出したスレッドだけを制限します。同じタスクアリーナで実行される他のスレッドは、this_task_arena::isolate によって分離されない限り、タスク選択は制限されません。

次の例は、this_task_arena::isolate を使用して、入れ子になった並列構造の呼び出し中にスレッドローカル変数が予期せず変更されないようにします。

#include "tbb/task_arena.h"
#include "tbb/parallel_for.h"
#include "tbb/enumerable_thread_specific.h"
#include <cassert>

int main() {
    const int N1 = 1000, N2 = 1000;
    tbb::enumerable_thread_specific<int> ets;
    tbb::parallel_for( 0, N1, [&ets]( int i ) {
        // スレッド固有の値を設定
        ets.local() = i;
        // 現在のスレッドが外部並列ループに関連するタスクを実行しないよう、
        // 分離した領域で 2 番目の並列ループを実行。
        tbb::this_task_arena::isolate( []{
            tbb::parallel_for( 0, N2, []( int j ) { /* 実際のワーク */ } );
        } );
        assert( ets.local()==i ); // 有効なアサーション
    } );
    return 0;
}

上位トピック: インテル® TBB デベロッパー・ガイド
https://software.intel.com/node/1465a8c9-7444-49ec-ae4a-b18afb603a5f

関連情報

タスク・スケジューラー (デベロッパー・リファレンス) (英語)
task_arena クラスと this_task_arena 名前空間 (英語)

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました