C 生产者消费者使用OpenMP任务

C 生产者消费者使用OpenMP任务,c,multithreading,task,openmp,C,Multithreading,Task,Openmp,我正在尝试使用OpenMP中的任务实现一个并行算法。 并行编程模式基于生产者-消费者的思想,但是 由于消费者流程比生产者慢,我想使用一些 生产者和若干消费者。 其主要思想是创建尽可能多的操作系统线程作为生产者,然后每个线程 这些将创建(由使用者)并行完成的任务。每一个 生产者将与一定比例的消费者(即 numCheckers/numSeekers)。 我在Intel双芯片服务器上运行该算法,每个芯片有6个内核。 问题是,当我只使用一个制作人(探索者)和越来越多的人 在消费者(跳棋者)中,性能随着

我正在尝试使用OpenMP中的任务实现一个并行算法。 并行编程模式基于生产者-消费者的思想,但是 由于消费者流程比生产者慢,我想使用一些 生产者和若干消费者。 其主要思想是创建尽可能多的操作系统线程作为生产者,然后每个线程 这些将创建(由使用者)并行完成的任务。每一个 生产者将与一定比例的消费者(即 numCheckers/numSeekers)。 我在Intel双芯片服务器上运行该算法,每个芯片有6个内核。 问题是,当我只使用一个制作人(探索者)和越来越多的人 在消费者(跳棋者)中,性能随着 消费者不断增长(见下表),即使需要正确数量的内核 以100%的速度工作。 另一方面,如果我增加制作人的数量,平均时间 减少或至少保持稳定,即使有成比例的 消费者。 在我看来,所有的改进都是通过输入的划分来实现的 在生产者中,任务只是窃听。但是,我也没有 与一位制作人解释该行为。我是不是遗漏了什么 OpenMP任务逻辑?我做错什么了吗

-------------------------------------------------------------------------
|   producers   |   consumers   |   time        |
-------------------------------------------------------------------------
|       1       |       1       |   0.642935    |
|       1       |       2       |   3.004023    |
|       1       |       3       |   5.332524    |
|       1       |       4       |   7.222009    |
|       1       |       5       |   9.472093    |
|       1       |       6       |   10.372389   |
|       1       |       7       |   12.671839   |
|       1       |       8       |   14.631013   |
|       1       |       9       |   14.500603   |
|       1       |      10       |   18.034931   |
|       1       |      11       |   17.835978   |
-------------------------------------------------------------------------
|       2       |       2       |   0.357881    |
|       2       |       4       |   0.361383    |
|       2       |       6       |   0.362556    |
|       2       |       8       |   0.359722    |
|       2       |      10       |   0.358816    |
-------------------------------------------------------------------------
我的代码的主要部分如下:

int main( int argc, char** argv) {

  // ... process the input (read from file, etc...)

  const char *buffer_start[numSeekers];
  int buffer_len[numSeekers];

  //populate these arrays dividing the input
  //I need to do this because I need to overlap the buffers for
  //correctness, so I simple parallel-for it's not enough 

  //Here is where I create the producers
  int num = 0;
  #pragma omp parallel for num_threads(numSeekers) reduction(+:num)
  for (int i = 0; i < numSeekers; i++) {
      num += seek(buffer_start[i], buffer_len[i]);
  }

  return (int*)num;
}

int seek(const char* buffer, int n){

  int num = 0;

  //asign the same number of consumers for each producer 
  #pragma omp parallel num_threads(numCheckers/numSeekers) shared(num)
  {
    //only one time for every producer
    #pragma omp single
    {
      for(int pos = 0; pos < n; pos += STEP){
    if (condition(buffer[pos])){
      #pragma omp task shared(num)
      {
        //check() is a sequential function
        num += check(buffer[pos]);
      }
    }
      }
      #pragma omp taskwait
    }
  return num;
}
int main(int argc,char**argv){
//…处理输入(从文件读取等…)
常量字符*缓冲区启动[numsekers];
int buffer_len[numsekers];
//填充这些分割输入的数组
//我需要这样做,因为我需要重叠
//正确性,所以我认为简单的并行是不够的
//这里是我创建制作人的地方
int num=0;
#pragma omp parallel用于num_线程(numsekers)缩减(+:num)
对于(int i=0;i
正如Hristo在评论中所建议的那样,您应该启用嵌套并行。这是通过设置环境变量完成的:

static double ssumt;
#pragma omp threadprivate(ssumt)

#pragma omp parallel
{
   ssumt = 0.0;

   #pragma omp single
   for (int i = 0; i < 10000000; i++) {
      #pragma omp task
      {
         ssumt += sin(i*0.001);
      }
   }
   #pragma omp taskwait

   #pragma omp atomic
   ssum += ssumt;
}
  • OMP\u嵌套
    (启用或禁用嵌套并行)
  • OMP\u MAX\u ACTIVE\u LEVELS
    (控制嵌套活动并行区域的最大数量)
另一方面,我建议采用以下策略,而不是使用
原子结构来保护累积:

...
// Create a local buffer to accumulate partial results
const int nthreads = numCheckers/numSeekers;
const int stride   = 32; // Choose a value that avoids false sharing
int numt[stride*nthreads];
// Initialize to zero as we are reducing on + operator
for (int ii = 0; ii < stride*nthreads; ii++)    
  numt[ii] = 0;

#pragma omp parallel num_threads(numCheckers/numSeekers)
{

  //only one time for every producer
  #pragma omp single
  {
    for(int pos = 0; pos < n; pos += STEP){
      if (condition(buffer[pos])){
      #pragma omp task
      {
        //check() is a sequential function
        const int idx = omp_get_thread_num();
        numt[idx*stride] += check(buffer[pos]);
      }
    }
  }
  #pragma omp taskwait

  // Accumulate partial results
  const int idx = omp_get_thread_num();
  #pragma atomic
  num += numt[stride*idx];
}
。。。
//创建本地缓冲区以累积部分结果
const int nthreads=numCheckers/numsekers;
const int stride=32;//选择一个避免错误共享的值
整数[stride*nthreads];
//初始化为零,因为我们正在减少+运算符
对于(int ii=0;ii
这应该可以防止由于同时请求在同一内存位置上写入而导致的潜在减速

请注意,先前版本的答案建议在最内侧的平行区域中使用
缩减
,这是错误的,因为:

出现在最内层的reduce子句中的列表项 封闭工作共享或并行构造不能在中访问 明确任务


OpenMP 3.1规范的§2.9.3.6不允许。观察到的行为是由于您没有启用嵌套的
并行
区域。发生的情况是,在第一种情况下,您实际上正在经历OpenMP任务的巨大开销。这很可能是由于
检查()
与OpenMP运行时引入的开销相比,没有完成足够的工作。为什么1和2生产者的行为如此

当仅使用一个生产者运行时,外部的
并行
区域仅使用一个线程执行。根据OpenMP API规范,此类
并行
区域处于非活动状态,并且它们仅串行执行内部代码(唯一的开销是额外的函数调用和通过指针访问共享变量)。在这种情况下,内部
并行
区域虽然在禁用嵌套并行的情况下嵌套,但会变为活动状态并引发大量任务。任务会引入相对较高的开销,并且此开销会随着线程数目的增加而增加。对于1个使用者,内部
并行
区域也处于非活动状态,因此会连续运行没有任务开销

当使用两个生产者运行时,外部
并行
区域处于活动状态,因此内部
并行
区域处于非活动状态(请记住-未启用嵌套并行),因此根本不会创建任务-
seek()
只是串行运行。没有任务开销,代码的运行速度几乎是1生产者/1消费者案例的两倍。运行时间不取决于消费者的数量,因为内部
并行
区域始终处于非活动状态,无论有多少消费者
#pragma omp parallel
{
   #pragma omp single
   for (int i = 0; i < 10000000; i++) {
      #pragma omp task
      {
         #pragma omp atomic
         ssum += sin(i*0.001);
      }
   }
}
#define STRIDE 8

#pragma omp parallel
{
   #pragma omp single
   for (int i = 0; i < 10000000; i++) {
      #pragma omp task
      {
         const int idx = omp_get_thread_num();
         ssumt[idx*STRIDE] += sin(i*0.001);
      }
   }
   #pragma omp taskwait

   const int idx = omp_get_thread_num();
   #pragma omp atomic
   ssum += ssumt[idx*STRIDE];
}
g++ -fopenmp -o test.exe test.cc
static double ssumt;
#pragma omp threadprivate(ssumt)

#pragma omp parallel
{
   ssumt = 0.0;

   #pragma omp single
   for (int i = 0; i < 10000000; i++) {
      #pragma omp task
      {
         ssumt += sin(i*0.001);
      }
   }
   #pragma omp taskwait

   #pragma omp atomic
   ssum += ssumt;
}