ここでは、プログラム実行の 2 つの基本タイプ (データ並列処理とタスク並列処理) と、タスクのそれぞれのパターンについて説明します。
多くのプログラムでは、ワークのほとんどがデータ要素の処理であり、そのほとんどはループで行われます。データ並列処理パターンは、そのような状況向けに設計されています。それぞれのデータ要素またはそのサブセットを異なるタスクのインスタンスで処理します。一般に、並列サイトはそれぞれのデータ要素の処理を呼び出すコードを含み、タスクで処理が行われます。
典型的なパターンでは、シリアルプログラムはデータ要素を反復するループを持ち、ループの本体で順番にそれぞれのデータ要素を処理します。データ並列処理パターンは、ループ全体を並列サイトとし、ループ本体をタスクとします。単純な C++ コードについて考えてみます。
ANNOTATE_SITE_BEGIN(sitename);
for (int I = 0; I != n; ++I) {
ANNOTATE_ITERATION_TASK(task_process);
process(a[i]);
}
ANNOTATE_SITE_END();次の C/C++ コードでは、処理されるデータ要素がツリーのノードにあります。再帰的なツリー処理は並列サイトのシリアル実行の一部であり、process_node 呼び出しのみが異なるタスクで実行されます。
ANNOTATE_SITE_BEGIN(sitename);
process_subtree(root);
ANNOTATE_SITE_END(sitename);
. . .
void process_subtree(node) // 並列サイトの動的範囲
{
ANNOTATE_TASK_BEGIN(task_process);
process_node(node);
ANNOTATE_TASK_END();
for (child = first_child(node);
child;
child = next_child(child) )
{
process_subtree(child);
}
}データ並列パターンにおいて、並列サイトは通常単一のタスクを含みます。
サンプル tachyon_Advisor は、データ並列処理の例を示します。
ワークを個別に並列化できない複数のアクティビティーに分割する場合、タスク並列処理を利用できる可能性があります。
タスク並列処理においてタスクという用語は、一般にアクティビティーまたはジョブという意味で使用されます。「ほかのコード本体とは独立して実行されるコード本体」を表すのに同じ用語が使用されるのは偶然です。
このパターンでは、異なるアクティビティーを同時に実行する並列サイトに、複数の異なるタスク本体があります。
前述の例では、表示と更新操作は個別に並列化できないと仮定します。しかし、表示と更新を同時に行える可能性があります。次の C++ コードについて考えてみます。
initialize(data);
while (!done) {
old_data = data;
ANNOTATE_SITE_BEGIN(sitename);
ANNOTATE_TASK_BEGIN(task_display);
display_on_screen(old_data);
ANNOTATE_TASK_END();
ANNOTATE_TASK_BEGIN(task_updatedata);
update(data);
ANNOTATE_TASK_END();
ANNOTATE_SITE_END();
}タスク並列パターンの短所は、タスクの数よりも多いコア数の利点を活用できないことです。この例では、3 つ以上のコアは利用されません。しかし、タスク並列パターンは、データ並列パターンを適用できないプログラムで利用できる可能性があります。
タスク並列処理で使用されるタスクは、呼び出される関数に制限されません。例えば、変数X と Y を個別にインクリメントする 2 つのタスクを生成する C/C++ コードを考えてみます。
main() {
ANNOTATE_SITE_BEGIN(sitename);
ANNOTATE_TASK_BEGIN(task_x);
X++;
ANNOTATE_TASK_END();
ANNOTATE_TASK_BEGIN(task_y);
Y++;
ANNOTATE_TASK_END();
ANNOTATE_SITE_END();
}サンプル stats は、タスク並列処理の例を示します。