8
Node *head = &node1;
while (head)
{
    #pragma omp task 
        cout<<head->value<<endl;
    head = head->next;
}

#pragma omp parallel
{
    #pragma omp single
    {
        Node *head = &node1;
        while (head)
        {
            #pragma omp task 
                cout<<head->value<<endl;
            head = head->next;
        }
    }
}

最初のブロックでは、パラレル ディレクティブを使用せずにタスクを作成しましたが、2 番目のブロックでは、論文で見た一般的な方法であるパラレル ディレクティブと単一のディレクティブを使用しました。それらの違いは何ですか?ところで、私はこれらのディレクティブの基本的な意味を知っています。

私のコメントのコード:

void traverse(node *root)
{
    if (root->left) 
    {
        #pragma omp task 
        traverse(root->left);
    }
    if (root->right) 
    {
        #pragma omp task 
        traverse(root->right);
    }
    process(root);
}
4

1 に答える 1

15

違いは、最初のブロックでは実際にはタスクを作成していないということです。これは、ブロック自体が (構文的にも字句的にも) アクティブな並列領域内にネストされていないためです。2 番目のブロックでは、taskコンストラクトが領域内に構文的にネストされ、parallel実行時に領域がアクティブになった場合に明示的なタスクをキューに入れます (アクティブな並列領域とは、複数のスレッドのチームで実行される領域です)。字句の入れ子はそれほど明白ではありません。次の例に注意してください。

void foo(void)
{
   int i;

   for (i = 0; i < 10; i++)
      #pragma omp task
      bar();
}

int main(void)
{
   foo();

   #pragma omp parallel num_threads(4)
   {
      #pragma omp single
      foo();
   }

   return 0;
}

への最初の呼び出しfoo()は、並列領域の外で発生します。したがって、taskディレクティブは (ほとんど) 何もせず、すべての呼び出しがbar()連続して発生します。の 2 番目の呼び出しfoo()は、並列領域内から行われるため、新しいタスクが 内で生成されfoo()ます。句によってスレッド数が固定されているため、parallelリージョンはアクティブです。4num_threads(4)

OpenMP ディレクティブのこの異なる動作は設計機能です。主なアイデアは、シリアルとパラレルの両方で実行できるコードを記述できるようにすることです。

それでも、taskコンストラクトが存在すると、foo()コード変換が行われます。たとえばfoo()、次のように変換されます。

void foo_omp_fn_1(void *omp_data)
{
   bar();
}

void foo(void)
{
   int i;

   for (i = 0; i < 10; i++)
      OMP_make_task(foo_omp_fn_1, NULL);
}

これは、最初の引数として指定された関数への呼び出しをキューに入れる、 OpenMPOMP_make_task()サポート ライブラリの架空の (公開されていない) 関数です。OMP_make_task()アクティブな並列領域の外で動作することが検出された場合は、foo_omp_fn_1()代わりに呼び出すだけです。bar()これにより、シリアルの場合の呼び出しにいくらかのオーバーヘッドが追加されます。の代わりにmain -> foo -> bar、呼び出しは次のようになりmain -> foo -> OMP_make_task -> foo_omp_fn_1 -> barます。これは、シリアル コードの実行が遅くなることを意味します。

これは、ワークシェアリング ディレクティブでさらに明確に示されます。

void foo(void)
{
   int i;

   #pragma omp for
   for (i = 0; i < 12; i++)
      bar();
}

int main(void)
{
   foo();

   #pragma omp parallel num_threads(4)
   {
      foo();
   }

   return 0;
}

への最初の呼び出しfoo()は、ループをシリアルに実行します。2 番目の呼び出しでは、12 回の反復が 4 つのスレッドに分散されます。つまり、各スレッドは 3 回の反復しか実行しません。繰り返しになりますが、これを実現するために何らかのコード変換マジックが使用されており、シリアル ループは に no#pragma omp forが存在する場合よりも遅く実行されfoo()ます。

ここでの教訓は、実際には必要のない場所に OpenMP コンストラクトを追加しないことです。

于 2012-11-17T16:38:43.237 に答える