OMP部分中的线程数

时间:2015-11-10 14:16:53

标签: c++ multithreading openmp

我的电脑有四个核心。我正在运行Ubuntu 15.10,并使用g ++ -fopenmp进行编译......

我有两种不同类型的工作,两者都是相互独立的:Work1和Work2。特别是,Work1应该在单个处理器上运行,但Work2应该并行化。我尝试使用omp_set_num_threads():

#pragma omp parallel sections
{
    #pragma omp section
    {
        // Should run on one processor.
        omp_set_num_threads(1);
        Work1();
    }

    #pragma omp section
    {
        // Should run on as many processors as possible.
        omp_set_num_threads(3);
        Work2();
    }
}

Say Work2是这样的:

void Work2(...){
    #pragma omp parallel for
    for (...) ...

    return;
}

运行程序时,只使用两个处理器。显然omp_set_num_threads()没有像我预期的那样工作。有没有什么可以使用OpenMP来解决这种情况?

感谢所有人,

罗德里戈

2 个答案:

答案 0 :(得分:5)

首先,OpenMP标准不保证两个部分将由不同的线程执行(第2.7.2节" sections构造"):

  

在团队中的线程之间调度结构化块的方法是实现定义的。

使两个工作例程并发执行的唯一可靠方法是使用基于线程ID的显式流控制:

#pragma omp parallel num_threads(2)
{
   if (omp_get_thread_num() == 0)
   {
      omp_set_num_threads(1);
      Work1();
   }
   else
   {
      omp_set_num_threads(3);
      Work2();
   }
}

此外,Work2()中的嵌套并行区域是否将使用多个线程,取决于多种因素的组合。这些因素包括几个内部控制变量(ICV)的值:

  • nest-var 控制是否启用嵌套并行机制;从OMP_NESTED的值初始化并通过调用omp_set_nested();
  • 在运行时设置
  • thread-limit-var (因为OpenMP 3.0)设置所有活动并行区域中所有OpenMP线程总量的上限;从OMP_THREAD_LIMIT的值初始化并通过应用thread_limit子句在运行时设置;
  • max-active-levels (因为OpenMP 3.0)限制了活动并行区域的深度;从OMP_MAX_ACTIVE_LEVELS的值初始化,并通过调用omp_set_max_active_levels()设置。

如果 nest-var 为false,则其他ICV的值无关紧要 - 禁用嵌套并行性。这是标准规定的默认值,因此必须明确启用嵌套并行性。

如果启用了嵌套并行性,则它仅在最高 max-active-levels 的级别上工作,最外层的并行区域为1级,第一个嵌套的并行区域为2级,等等。该ICV的默认值是实现支持的嵌套并行度级别数。更深层次的并行区域被禁用,即仅与其主线程串行执行。

如果启用了嵌套并行性并且某个特定并行区域嵌套在不超过 max-active-levels 的级别,那么它是否将并行执行由<的值确定EM>线程限制-VAR 。在您的情况下,任何小于4的值都将导致Work2()无法使用三个线程执行。

以下测试程序可用于检查这些ICV之间的相互作用:

#include <stdio.h>
#include <omp.h>

void Work1(void)
{
   printf("Work1 started by tid %d/%d\n",
      omp_get_thread_num(), omp_get_num_threads());
}

void Work2(void)
{
   printf("Work2 started by tid %d/%d\n",
      omp_get_thread_num(), omp_get_num_threads());

   #pragma omp parallel for schedule(static)
   for (int i = 0; i < 3; i++)
   {
      printf("Work2 nested loop: %d by tid %d/%d\n", i,
         omp_get_thread_num(), omp_get_num_threads());
   }
}

int main(void)
{
   #pragma omp parallel num_threads(2)
   {
      if (omp_get_thread_num() == 0)
      {
         omp_set_num_threads(1);
         Work1();
      }
      else
      {
         omp_set_num_threads(3);
         Work2();
      }
   }
   return 0;
}

示例输出:

$ ./nested
Work1: started by tid 0/2
Work2: started by tid 1/2
Work2 nested loop: 0 by tid 0/1
Work2 nested loop: 1 by tid 0/1
Work2 nested loop: 2 by tid 0/1

最外面的平行区域是活动的。 Work2()中的嵌套版本处于非活动状态,因为默认情况下禁用嵌套并行。

$ OMP_NESTED=TRUE ./nested
Work1: started by tid 0/2
Work2: started by tid 1/2
Work2 nested loop: 0 by tid 0/3
Work2 nested loop: 1 by tid 1/3
Work2 nested loop: 2 by tid 2/3

所有并行区域都处于活动状态并且并行执行。

$ OMP_NESTED=TRUE OMP_MAX_ACTIVE_LEVELS=1 ./nested
Work1: started by tid 0/2
Work2: started by tid 1/2
Work2 nested loop: 0 by tid 0/1
Work2 nested loop: 1 by tid 0/1
Work2 nested loop: 2 by tid 0/1

尽管启用了嵌套并行,但只有一级并行可以处于活动状态,因此嵌套区域可以串行执行。使用pre-OpenMP 3.0编译器,例如GCC 4.4,设置OMP_MAX_ACTIVE_LEVELS无效。

$ OMP_NESTED=TRUE OMP_THREAD_LIMIT=3 ./nested
Work1: started by tid 0/2
Work2: started by tid 1/2
Work2 nested loop: 0 by tid 0/2
Work2 nested loop: 2 by tid 1/2
Work2 nested loop: 1 by tid 0/2

嵌套区域处于活动状态,但由于设置OMP_THREAD_LIMIT所施加的全局线程限制,仅使用两个线程执行。

如果你已经启用了嵌套并行性,那么对活动级别的数量没有限制,并且线程限制足够高,你的程序应该没有理由不同时使用四个CPU内核......

...除非进程和/或线程绑定生效。绑定控制不同OpenMP线程与可用CPU的关联。对于大多数OpenMP运行时,默认情况下禁用线程绑定,并且OS调度程序可以自由地在可用内核之间移动线程,因为它认为合适。然而,运行时通常遵循适用于整个过程的亲和力掩模。如果您使用taskset之类的内容,例如将进程固定/绑定到两个逻辑CPU,然后无论生成多少个线程,它们都将在两个逻辑CPU和分时共享上运行。通过设置GOMP_CPU_AFFINITY来设置OMP_PROC_BIND和/或OMP_PLACES以及支持OpenMP 4.0的最新版本,可以控制GCC线程绑定。

如果您没有绑定可执行文件(通过检查Cpus_allowed/proc/$PID/status的值来验证,其中$PID是正在运行的OpenMP进程的PID),{{1}设置了{/ GOMP_CPU_AFFINITYOMP_PROC_BIND,启用了嵌套并行,对活动并行级别或线程数没有限制,OMP_PLACEStop等程序仍显示只使用了两个逻辑CPU,那么你的程序的逻辑就出现了问题,而不是OpenMP环境。

答案 1 :(得分:0)

在GCC中,部分是作为并行for和if-then或switch-case语句的组合实现的(或者至少它是在某一点以这种方式实现的)。为什么不亲自做这样的事呢?

#pragma omp parallel
{
    unsigned ithread = omp_get_thread_num();
    unsigned nthread = omp_get_num_threads();
    if(ithread==0)  work1();
    if(ithread!=0 || nthread==1) {
        //distribute work2 to nthread-1 threads.
        unsigned start = nthread==1 ? 0 : (ithread-1)*N/(nthread-1);
        unsigned end   = nthread==1 ? N :     ithread*N/(nthread-1);
        for(unsigned i=start; i<end; i++) {
            //work2 per iteration
        }
    }
}

这种方法有一些缺点。首先,它要求work1在特定线程上运行。其次,如果work1work2之前完成,那么该帖子无法完成work2

这是另一种使用动态调度解决这两个问题的方法。

#pragma omp parallel
{ 
    //while(1) {
    #pragma omp single nowait
    work1();
    #pragma omp for schedule(dynamic) nowait
    for(int i=0; i<N; i++) {
        //work2 to per iteration
    }
    //}
}

该方法的一个缺点在于动态调度的开销高于静态调度。但是,它不再需要work在特定线程上运行,并且如果执行work1的线程在执行work2的线程之前完成,那么该线程可以帮助work2。所以这种方法可以更好地平衡负载。