Oracle Solaris Studio 12.2: OpenMP API ユーザーガイド

第 4 章 入れ子並列処理

この章では、OpenMP の入れ子並列処理について説明します。

4.1 実行モデル

OpenMP は並列実行の fork-join モデルを使用しています。スレッドは並列構文を検出すると、自身を含めほかのスレッドとチームを構成します (ほかのスレッドがまったくないこともあります)。並列構文を検出したスレッドは、このチームのマスタースレッドとなり、チーム内のその他のスレッドは、スレーブスレッドとなります。すべてのスレッドは、並列構文内のコードを実行します。各スレッドは並列構文内での処理を終了すると、その並列構文の最後にある暗黙バリアで待ち状態となります。チーム内のすべてのスレッドがバリアで待ち状態に入れば、スレッドは解放されます。マスタースレッドだけは並列構文の処理が終了したあとも続けてユーザーコードを実行しますが、スレーブスレッドは今度は別のチームを構成するための呼び出しの待ち状態に入ります。

OpenMP での並列領域は、互いに入れ子にすることができます。スレッドが並列領域内で並列構文を検出してチームを作成する際に、入れ子並列処理が無効になっていると、チームに含まれるスレッドは並列構文を検出したスレッドだけとなります。入れ子並列処理が有効になっていれば、複数のスレッドでチームが作成されます。

OpenMP 実行時ライブラリにはスレッドがプールされていて、並列領域内でのスレーブスレッドとして使用されます。あるスレッドが並列構文の検出時に複数のスレッドで構成されるチームを作成する必要がある場合は、そのスレッドは、最初にプールを調べてアイドル状態のスレッドを選択し、自身のチームのスレーブスレッドにします。このとき、充分な数のアイドル状態のスレッドがプールにないと、マスタースレッドが取得できるスレーブスレッドの数は必要な数を満たさないこともあります。チームが並列領域での処理を完了すると、スレーブスレッドはプールに返されます。

4.2 入れ子並列処理の制御

入れ子並列処理は、プログラムの実行前にさまざまな環境変数を設定することでその実行を制御できます。

4.2.1 OMP_NESTED

入れ子並列処理は、OMP_NESTED 環境変数を設定するか omp_set_nested() を呼び出すことで有効または無効に設定できます。

3 つのレベルを持つ、入れ子並列構文の例を次に示します。


例 4–1 入れ子並列処理の例


#include <omp.h>
#include <stdio.h>
void report_num_threads(int level)
{
    #pragma omp single
    {
        printf("Level %d: number of threads in the team - %d\n",
                  level, omp_get_num_threads());
    }
 }
int main()
{
    omp_set_dynamic(0);
    #pragma omp parallel num_threads(2)
    {
        report_num_threads(1);
        #pragma omp parallel num_threads(2)
        {
            report_num_threads(2);
            #pragma omp parallel num_threads(2)
            {
                report_num_threads(3);
            }
        }
    }
    return(0);
}

入れ子並列処理を有効にして、このプログラムをコンパイルおよび実行すると、次のようなソート済みの結果が出力されます。


% setenv OMP_NESTED TRUE
% a.out
Level 1: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2

入れ子並列処理を無効にして同じプログラムを実行した場合と比べてみましょう。


% setenv OMP_NESTED FALSE
% a.out
Level 1: number of threads in the team - 2
Level 2: number of threads in the team - 1
Level 3: number of threads in the team - 1
Level 2: number of threads in the team - 1
Level 3: number of threads in the team - 1

4.2.2 OMP_THREAD_LIMIT

OpenMP 実行時ライブラリにはスレッドがプールされていて、並列領域内でのスレーブスレッドとして使用されます。OMP_THREAD_LIMIT 環境変数の設定は、プール内のスレッド数を制御します。デフォルトでは、プール内のスレッド数は最大で 1023 個です。

プールにあるのは、実行時ライブラリが作成した非ユーザースレッドだけです。最初のスレッドやユーザーのプログラムが明示的に作成したスレッドは含まれません。

OMP_THREAD_LIMIT が 1 に設定されている場合 (または SUNW_MP_MAX_POOL_THREADS がゼロに設定されている場合)、スレッドプールは空になり、すべての並列領域は 1 つのスレッドによって実行されます。

次の例では、プール内のスレッド数が不十分な場合、並列領域で取得されるスレッド数も少なくなることを示しています。ここでも、前の例と同じコードを使用します。アクティブ化されるすべての並列領域に必要な同時スレッドの数は、8 個です。つまり、プールには少なくとも 7 個のスレッドが含まれている必要があります。OMP_THREAD_LIMIT を 6 に設定した場合 (または SUNW_MP_MAX_POOL_THREADS を 5 に設定した場合)、プールには最大で 5 個のスレーブスレッドが含まれます。これは、内側から数えて 4 つの並列領域のうち 2 つが、必要な数のスレーブスレッドを取得できないことを示します。実行結果はさまざまですが、1 つの例を見てみましょう。


% setenv OMP_NESTED TRUE
% OMP_THREAD_LIMIT 6
% a.out
Level 1: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 1
Level 3: number of threads in the team - 1

4.2.3 OMP_MAX_ACTIVE_LEVELS

環境変数 OMP_MAX_ACTIVE_LEVELS は、スレッドを複数必要とする有効な並列領域を何層まで入れ子にすることができるかを制御します。

この環境変数で指定した数を超える有効な入れ子を持つ有効な並列領域は、1 つのスレッドによって実行されます。並列領域が有効であると見なされるのは、if 節がない場合と、if 節の評価が true となるときです。有効な入れ子レベルのデフォルトの最大数は 4 です。

次に、4 重の入れ子になった並列領域のコードの例を示します。OMP_MAX_ACTIVE_LEVELS が 2 に設定されると、3 番目と 4 番目の深さにある入れ子並列領域は 1 つのスレッドによって実行されます。


#include <omp.h>
#include <stdio.h>
#define DEPTH 5
void report_num_threads(int level)
{
    #pragma omp single
    {
        printf("Level %d: number of threads in the team - %d\n",
               level, omp_get_num_threads());
    }
}
void nested(int depth)
{
    if (depth == DEPTH)
        return;

    #pragma omp parallel num_threads(2)
    {
        report_num_threads(depth);
        nested(depth+1);
    }
}
int main()
{
    omp_set_dynamic(0);
    omp_set_nested(1);
    nested(1);
    return(0);
}

入れ子の深さの最大数を 4 に設定してこのプログラムをコンパイル、実行すると、次のような結果が出力されます。実際の結果は、OS がどのようにスレッドをスケジューリングしているかによって異なります。


% setenv OMP_MAX_ACTIVE_LEVELS 4
% a.out |sort 
Level 1: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 3: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2
Level 4: number of threads in the team - 2

入れ子の深さを 2 に設定して実行した場合の結果は次のとおりです。


% setenv OMP_MAX_ACTIVE_LEVELS 2
% a.out |sort 
Level 1: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 2: number of threads in the team - 2
Level 3: number of threads in the team - 1
Level 3: number of threads in the team - 1
Level 3: number of threads in the team - 1
Level 3: number of threads in the team - 1
Level 4: number of threads in the team - 1
Level 4: number of threads in the team - 1
Level 4: number of threads in the team - 1
Level 4: number of threads in the team - 1

この例は、可能性のある結果の一部のみを示しています。実際の結果は、OS がどのようにスレッドをスケジューリングしているかによって異なります。

4.3 入れ子並列領域での OpenMP ライブラリルーチンの使用

ここでは、入れ子並列領域内で次の OpenMP ルーチンを呼び出す実行について説明します。

- omp_set_num_threads()
- omp_get_max_threads()
- omp_set_dynamic()
- omp_get_dynamic()
- omp_set_nested()
- omp_get_nested()

「set」呼び出しは、呼び出しスレッドが検出した並列領域と同じレベルまたはその内側で入れ子になっている、呼び出し以降の並列領域に対してのみ有効です。ほかのスレッドが検出した並列領域には無効です。

「get」呼び出しは、呼び出しスレッドが設定した値を返します。スレッドが並列領域の実行時にチームのマスターになる場合は、チームのほかのすべてのメンバーはマスタースレッドが持つ値を継承します。マスタースレッドが入れ子並列領域を終了し、その領域を取り囲む並列領域の実行を続ける場合、そのスレッドの値は、入れ子並列領域を実行する直前に、取り囲んでいる並列領域内での値に戻ります。


例 4–2 並列領域内での OpenMP ルーチンの呼び出し


#include <stdio.h>
#include <omp.h>
int main()
{
    omp_set_nested(1);
    omp_set_dynamic(0);
    #pragma omp parallel num_threads(2)
    {
        if (omp_get_thread_num() == 0)
            omp_set_num_threads(4);       /* line A */
        else
            omp_set_num_threads(6);       /* line B */

        /* The following statement will print out
         *
         * 0: 2 4
         * 1: 2 6
         *
         * omp_get_num_threads() returns the number
         * of the threads in the team, so it is
         * the same for the two threads in the team.
         */
        printf("%d: %d %d\n", omp_get_thread_num(),
               omp_get_num_threads(),
               omp_get_max_threads());

        /* Two inner parallel regions will be created
         * one with a team of 4 threads, and the other
         * with a team of 6 threads.
         */
        #pragma omp parallel
        {
            #pragma omp master
            {
                /* The following statement will print out
                 *
                 * Inner: 4
                 * Inner: 6
                 */
                printf("Inner: %d\n", omp_get_num_threads());
            }
            omp_set_num_threads(7);      /* line C */
        }

        /* Again two inner parallel regions will be created,
         * one with a team of 4 threads, and the other
         * with a team of 6 threads.
         *
         * The omp_set_num_threads(7) call at line C
         * has no effect here, since it affects only
         * parallel regions at the same or inner nesting
         * level as line C.
         */

        #pragma omp parallel
        {
            printf("count me.\n");
        }
    }
    return(0);
}

このプログラムをコンパイル、実行すると次のような結果が出力されます。


% a.out
0: 2 4
Inner: 4
1: 2 6
Inner: 6
count me.
count me.
count me.
count me.
count me.
count me.
count me.
count me.
count me.
count me.

4.4 入れ子並列処理を使う際のヒント