関数 Foo を配列の各要素に適用させ、各要素を同時に処理しても安全だと仮定します。シーケンシャル・コードは次のようになります。
void SerialApplyFoo( float a[], size_t n ) { for( size_t i=0; i!=n; ++i ) Foo(a[i]); }
ここでの反復空間は、size_t 型で、範囲は 0 から n-1 までです。テンプレート関数 tbb::parallel_for は、反復空間をチャンクに分け、個別のスレッドで各チャンクを実行します。このループの並列化における最初のステップは、ループボディーをチャンクで動作するような形式に変換することです。この形式は、標準テンプレート・ライブラリー (STL) スタイルのボディー と呼ばれる関数オブジェクトで、operator() がチャンクを処理します。次のコードは、ボディー・オブジェクトを宣言します。インテル® TBB に必要なコードは、太字で示されています。
#include "tbb/tbb.h" using namespace tbb; class ApplyFoo { float *const my_a; public: void operator()( const blocked_range<size_t>& r ) const { float *a = my_a; for( size_t i=r.begin(); i!=r.end(); ++i ) Foo(a[i]); } ApplyFoo( float a[] ) : my_a(a) {} };
サンプルの using 宣言子は、各識別子の前に名前空間のプリフィクス tbb を付けなくてもライブラリー識別子を使用できるようにします。サンプルの残りの部分は、using 宣言子が存在していると仮定しています。
operator() の引数に注目してください。blocked_range<T> は、ライブラリーによって提供されるテンプレート・クラスで、型 T の 1 次元の反復空間を示します。parallel_for クラスは、別の反復空間とも動作します。ライブラリーは、2 次元空間用に blocked_range2d を提供します。「高度なトピック: 異なる種類の反復空間」で説明されているように独自の空間を定義することもできます。
ApplyFoo のインスタンスでは、オリジナルのループの外で定義されるがループ内で使用される、すべてのローカル変数を認識するメンバーフィールドが必要です。通常、ボディー・オブジェクトのコンストラクターはこれらのフィールドを初期化しますが、parallel_for は、ボディー・オブジェクトが作成される方法は問いません。テンプレート関数 parallel_for では、ボディー・オブジェクトに、各ワーカースレッドの個別のコピー (または複数のコピー) を作成するために起動されるコピー・コンストラクターが必要です。また、コピー・コンストラクターはこれらのコピーを破棄するデストラクターも起動します。ほとんどの場合、暗黙的に生成されたコピー・コンストラクターとデストラクターは正常に動作します。正常に動作しない場合は、両方 に一貫性がない場合がほとんどです。
ボディー・オブジェクトはコピーされることがあるため、その operator() ではボディーの変更はできません。変更された場合は、operator() がオリジナルとコピーのどちらで動作するかによって、parallel_for を起動するスレッドに対する可視性が変わります。この場合の注意点は、parallel_for がボディー・オブジェクトの operator() を const として宣言する必要があることです。
サンプルの operator() は、my_a をローカル変数の a にロードします。この必須ではない処理を行う理由は 2 つあります。
スタイル。ループボディーをよりオリジナルに近付けます。
パフォーマンス。ローカル変数はコンパイラーにより追跡されやすいため、頻繁にアクセスされる値をローカル変数に置くことで、コンパイラーはループをより効果的に最適化できます。
ループボディーをボディー・オブジェクトとして記述すると、次のようにテンプレート関数 parallel_for を起動できます。
#include "tbb/tbb.h" void ParallelApplyFoo( float a[], size_t n ) { parallel_for(blocked_range<size_t>(0,n), ApplyFoo(a)); }
ここで構築されている blocked_range は、0 から n-1 までの反復空間全体を表し、parallel_for は、各プロセッサーにサブ空間を分割します。コンストラクターの一般的な形式は、blocked_range<T>(begin,end,grainsize) です。T は、値の型を指定します。引数 begin と end は、反復空間を半開区間 [begin,end) として STL スタイルで指定します。引数 grainsize の説明は、「チャンクの制御」を参照してください。デフォルトの parallel_for はデフォルトの粒度で適切に動作するヒューリスティックを適用するため、サンプルではデフォルトの粒度 1 を使用しています。