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() 来启用或禁用嵌套并行操作。

以下示例中的嵌套并行构造具有三个级别。


示例 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 设置为零),线程池将为空,所有并行区域都将由一个线程来执行。

以下示例表明,如果池中的线程不足,并行区域将获得较少的线程。代码与上一示例相同。使所有并行区域同时处于活动状态所需的线程数为 8 个。所以,池至少需要包含 7 个线程。如果将 OMP_THREAD_LIMIT 设置为 6(或将 SUNW_MP_MAX_POOL_THREADS 设置为 5),池最多包含 5 个从属线程。这意味着四个最里面的并行区域中的两个区域可能无法获取所请求的所有从属线程。一种可能的结果如下所示。


% 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 可控制需要多个线程的嵌套活动并行区域的最大深度。

活动嵌套深度大于此环境变量值的任何活动并行区域将仅由一个线程来执行。如果并行区域没有 if 子句,或者其 if 子句计算为 true,会将此区域视为活动区域。活动嵌套级别的缺省最大数量是 4。

以下代码将创建 4 级嵌套并行区域。如果将 OMP_MAX_ACTIVE_LEVELS 设置为 2,嵌套深度为 3 和 4 的嵌套并行区域将由单个线程来执行。


#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 来编译和运行此程序会产生以下可能的输出。(实际结果取决于操作系统调度线程的方式。)


% 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

此外,这些示例只显示了一些可能的结果。实际结果取决于操作系统调度线程的方式。

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 有关使用嵌套并行操作的一些提示