次のコードは、最初の(外側の)ループを単に並列化しますか、それともネストされたループ全体を並列化しますか?
#pragma omp parallel for
for (int i=0;i<N;i++)
{
for (int j=0;j<M;j++)
{
//do task(i,j)//
}
}
上記のコードがネストされたforループ全体を並列化する(つまり、1つのスレッドが直接関連するtask(i、j))か、外側のforループのみを並列化する(したがって、各並列に対してループインデックスiのスレッド、その内部ループは単一のスレッドで順次実行されます。これは非常に重要です)。
記述した行は、外側のループのみを並列化します。両方を並列化するには、collapse
句を追加する必要があります。
#pragma omp parallel for collapse(2)
for (int i=0;i<N;i++)
{
for (int j=0;j<M;j++)
{
//do task(i,j)//
}
}
詳細については、 OpenMP 3.1 仕様(秒2.5.1)を確認してください。
次の例でこれをよりよく理解できます。これを2つのスレッドで行いましょう。
#pragma omp parallel for num_threads(2)
for(int i=0; i< 3; i++) {
for (int j=0; j< 3; j++) {
printf("i = %d, j= %d, threadId = %d \n", i, j, omp_get_thread_num());
}
}
結果は次のようになります。
i = 0, j= 0, threadId = 0
i = 0, j= 1, threadId = 0
i = 0, j= 2, threadId = 0
i = 1, j= 0, threadId = 0
i = 1, j= 1, threadId = 0
i = 1, j= 2, threadId = 0
i = 2, j= 0, threadId = 1
i = 2, j= 1, threadId = 1
i = 2, j= 2, threadId = 1
つまり、#pragma omp parallel forを最上部のforループに追加すると、そのforループのインデックスはスレッド間で分割されます。ご覧のとおり、iのインデックスが同じ場合、スレッドIDも同じです。
その代わりに、ネストされたforループにある組み合わせを並列化できます。この例では、次のiとjの組み合わせを使用できます。
i = 0, j= 0
i = 0, j= 1
i = 0, j= 2
i = 1, j= 0
i = 1, j= 1
i = 1, j= 2
i = 2, j= 0
i = 2, j= 1
i = 2, j= 2
コードの組み合わせを賢明に並列化するために、次のようにcollapseキーワードを追加できます。
#pragma omp parallel for num_threads(2) collapse(2)
for(int i=0; i< 3; i++) {
for (int j=0; j< 3; j++) {
printf("i = %d, j= %d, threadId = %d \n", i, j, omp_get_thread_num());
}
}
結果は次のようになります。
i = 0, j= 0, threadId = 0
i = 0, j= 1, threadId = 0
i = 1, j= 2, threadId = 1
i = 2, j= 0, threadId = 1
i = 2, j= 1, threadId = 1
i = 2, j= 2, threadId = 1
i = 0, j= 2, threadId = 0
i = 1, j= 0, threadId = 0
i = 1, j= 1, threadId = 0
次に、以前とは異なり、同じインデックスiに対して異なるスレッドIDが存在することがわかります((i = 1およびj = 2 threadId = 1)の場合(i = 1およびj = 0 threadId = 0))。つまり、このシナリオでは、iとjの組み合わせがスレッド間で分割されます。
OpenMPは、プラグマの隣のループのみを並列化します。必要に応じて、内側のループも並列化できますが、自動的には実行されません。