トークンベース・システムの作成#
フローグラフ内のメッセージ数を制限するより柔軟な解決策として、トークンを使用する方法があります。トークンベースのシステムでは、グラフ内で使用できるトークンの数は限られており、メッセージは、使用可能なトークンとペアになるまでグラフに入ることができません。メッセージがグラフから削除されると、そのトークンが解放され、新しいメッセージとペアになって、入力が許可されます。
oneapi::tbb::parallel_pipeline アルゴリズムはトークンベースのシステムに依存します。フロー・グラフ・インターフェイスでは、トークンは明示的にサポートされていませんが、join_node を使用して同様のシステムを作成できます。join_node には、入力のタイプを記述するタプルとバッファーポリシーの 2 つのテンプレート引数があります。
template<typename OutputTuple, graph_buffer_policy JP = queueing>
class join_node;バッファーポリシーは以下のいずれかです。
queueing。このタイプのポリシーでは、入力が先入先出 (FIFO) 方式で照合されます。つまり、入力は受信順に結合されてタプルが形成されます。tag_matching。このタイプのポリシーは、一致するタグを持つ入力を結合します。reserving。このタイプのポリシーにより、join_nodeは内部的にバッファリングを行わず、上流ソースから各ポートの入力を最初に予約できる場合にのみ入力を消費します。各ポートで入力を予約できる場合は、それらの入力を取得し、それらを結合して出力タプルを形成します。
予約 join_nodes を使用することで、トークンベースのシステムを作成できます。
以下の例では、M 個の大きなオブジェクトを生成する input_node と、3 つのトークンが事前入力される buffer_node があります。token_t は任意の値にすることができます。例えば、typedef int token_t; などです。input_node と buffer_node は予約 join_node に接続されます。input_node は、予約 join_node によって入力がプルされた場合にのみ入力を生成し、予約 join_node は、buffer_node からプルする項目もあることを認識している場合にのみ、input_node から入力をプルします。
graph g;
int src_count = 0;
int number_of_objects = 0;
int max_objects = 3;
input_node< big_object * > s( g, [&]( oneapi::tbb::flow_control& fc ) -> big_object* {
if ( src_count < M ) {
big_object* v = new big_object();
++src_count;
return v;
} else {
fc.stop();
return nullptr;
}
} );
s.activate();
join_node< tuple_t, reserving > j(g);
buffer_node< token_t > b(g);
function_node< tuple_t, token_t > f( g, unlimited,
[]( const tuple_t &t ) -> token_t {
spin_for(1);
cout << get<1>(t) << "\n";
delete get<0>(t);
return get<1>(t);
} );
make_edge( s, input_port<0>(j) );
make_edge( b, input_port<1>(j) );
make_edge( j, f );
make_edge( f, b );
b.try_put( 1 );
b.try_put( 2 );
b.try_put( 3 );
g.wait_for_all();上記のコードでは、function_node がトークンを buffer_node に返すことがわかります。フローグラフのこのサイクルでは、トークンを再利用して input_node からの別の入力とペアにできます。したがって、前のセクションと同様に、グラフには最大 4 つの大きなオブジェクトが存在します。function_node には 3 つの大きなオブジェクトがあり、input_node には 1 つのオブジェクトがバッファリングされ、トークンとペアになるのを待機しています。
フローグラフには特定の token_t が定義されていないため、オブジェクトや配列へのポインターなど、任意のタイプのトークンを使用できます。したがって、上記の例とは異なり、token_t はダミータイプである必要はなく、例えば計算に不可欠なバッファーやその他のオブジェクトにできます。例えば、上記の例を変更して、大きなオブジェクト自体をトークンとして使用すると、割り当てと割り当て解除を繰り返し行う必要がなくなり、基本的に buffer_node へのサイクルバックを使用して大きな空きリストを作成できます。
また、上記の例では、buffer_node は try_put への明示的な呼び出しの固定数によって事前に入力されましたが、他のオプションもあります。例えば、input_node を buffer_node の入力に接続して、トークンを生成できます。さらに、function_node は、オプションで各出力ポートに 0 個以上の出力を配置できる multifunction_node に置き換えることもできます。multifunction_node を使用すると、トークンを再利用するか選択したり、さらにトークンを生成したりして、グラフ内で許可される同時実行性を増減できます。
トークンベースのシステムは非常に柔軟です。トークンを任意のタイプとして宣言し、実行中にトークンをシステムから挿入または削除できるため、システムで許可される同時実行性を動的に制御できます。トークンをソースの入力とペアにすることで、このアプローチによりグラフ全体のリソース消費を制限できます。