parallel_for#
関数 Foo を配列の各要素に適用させ、各プロセスを同時に処理しても安全だと仮定します。以下にシーケンシャル・コードを示します。
void SerialApplyFoo( float a[], size_t n ) {
for( size_t i=0; i!=n; ++i )
Foo(a[i]);
}この反復空間は size_t タイプであり、0 から n-1 までになります。テンプレート関数 oneapi::tbb::parallel_for は、反復空間をチャンクに分割し、個別のスレッドでそれぞれのチャンクを実行します。このループを並列化する最初のステップは、ループボディーをチャンクで動作する形式に変換することです。形式は、ボディー・オブジェクトと呼ばれる STL スタイルの関数オブジェクトであり、operator() がチャンクを処理します。次のコードは完全なボディー・オブジェクトを宣言しています。
#include "oneapi/tbb.h"
using namespace oneapi::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 ディレクティブは、各識別子の前に名前空間のプリフィクス oneapi::tbb を付けなくてもライブラリー識別子を使用できるようにします。例の残りの部分は、using ディレクティブが存在していると仮定しています。
operator() の引数に注意してください。blocked_range<T> は、ライブラリーによって提供されるテンプレート・クラスです。これは、T タイプ上の 1 次元の反復空間を示します。parallel_for クラスは別の反復空間とも動作します。ライブラリーは、多次元空間用に blocked_range2d、blocked_range3d、および blocked_nd_range を提供します。高度なトピック: その他の種類の反復空間で説明されているように、独自の空間を定義することもできます。
ApplyFoo のインスタンスでは、オリジナルのループの外に定義されて、ループ内で使用されるすべてのローカル変数を認識するメンバーフィールドが必要です。通常、ボディー・オブジェクトのコンストラクターはこれらのフィールドを初期化しますが、parallel_for は、ボディー・オブジェクトが作成される方法は問いません。テンプレート関数 parallel_for では、ボディー・オブジェクトにコピー・コンストラクターが必要です。コピー・コンストラクターは、各ワーカースレッドに個別のコピー (または複数のコピー) を作成するのに呼び出されます。また、これらのコピーを破棄するデストラクターも呼び出します。ほとんどの場合、暗黙的に生成されたコピー・コンストラクターとデストラクターは正しく動作します。そうでない場合は、ほとんどの場合 (C++ では通常どおり)、一貫性を保つため両方を定義する必要があります。
ボディー・オブジェクトはコピーされることがあるため、その operator() ではボディーの変更はできません。変更された場合、operator() がオリジナルまたはコピーで動作するかどうかによって、parallel_for を起動するスレッドの可視性が変わります。この意味を思い出させるため、parallel_for ではボディー・オブジェクトの operator() が const として宣言されている必要があります。
例中の operator() は my_a をローカル変数 a に読み込みます。この必須ではない処理を行うのには、2つの理由があります。
スタイル。 ループボディーをよりオリジナルに近付けます。
パフォーマンス。ローカル変数はコンパイラーにより追跡されやすいため、頻繁にアクセスされる値をローカル変数に置くことで、コンパイラーはループをより効果的に最適化できます。
ループボディーをボディ・オブジェクトとして記述したら、次のようにテンプレート関数 parallel_for を呼び出します。
#include "oneapi/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 は、反復空間を STL スタイルで半開区間 [begin,end) として指定します。引数の粒度については、Controlling_Chunking セクションで説明されています。この例では、デフォルトの粒度 1 を使用しています。これは、デフォルトでは、parallel_for がデフォルト粒度で適切に機能するヒューリスティックを適用するためです。