OpenMP并行尖峰

OpenMP parallel spiking

本文关键字:并行 OpenMP      更新时间:2023-10-16

我在Visual Studio 2010中使用OpenMP来加速循环。

我写了一个非常简单的测试来观察使用OpenMP的性能提升。我在空循环上使用omp parallel
int time_before = clock();
#pragma omp parallel for
for(i = 0; i < 4; i++){
}
int time_after = clock();
std::cout << "time elapsed: " << (time_after - time_before) << " milliseconds" << std::endl;

如果没有使用omp pragma,它始终需要0毫秒才能完成(如预期的那样),使用pragma通常也需要0毫秒。问题是使用opm pragma时,它偶尔会出现峰值,从10到32毫秒不等。每次我尝试并行使用OpenMP时,我都会得到这些随机峰值,所以我尝试了这个非常基本的测试。尖峰是OpenMP固有的一部分,还是可以避免?

并行的for在一些循环中给了我很大的速度提升,但是这些随机尖峰太大了,我无法使用它

这是很正常的行为。有时候你的操作系统很忙,需要更多的时间来生成新的线程。

我想补充一下kukis的答案:我还想说,峰值的原因是由于OpenMP带来的额外开销。

此外,由于您正在进行性能敏感的测量,我希望您在编译代码时打开了优化。在这种情况下,没有OpenMP的循环只是由编译器优化出来,因此在time_beforetime_after之间没有代码。然而,对于OpenMP,至少g++ 4.8.1 (-O3)无法优化代码:循环仍然存在于汇编器中,并且包含管理工作共享的附加语句。(我暂时无法尝试VS)

所以,比较是不公平的,因为没有OpenMP的那个被完全优化了。

编辑:您还必须记住,OpenMP不会每次都重新创建线程。相反,它使用线程池。因此,如果在循环之前执行一个omp-construct,那么当遇到另一个线程时,线程就已经创建好了:

// Dummy loop: Spawn the threads.
#pragma omp parallel for
for(int i = 0; i < 4; i++){
}
int time_before = clock();
// Do the actual measurement. OpenMP re-uses the threads.
#pragma omp parallel for
for(int i = 0; i < 4; i++){
}
int time_after = clock();

在这种情况下,峰值应该消失。

如果"OpenMP并行尖峰",我称之为"并行开销",是你循环中的一个问题,这就推断出你可能没有足够的工作负载来并行化。只有在问题规模足够大的情况下,并行化才能提高速度。您已经展示了一个极端的例子:在并行循环中没有工作。在这种情况下,由于并行开销,您将看到高度波动的时间。

OpenMP的omp parallel for中的并行开销包括以下几个因素:

  • 首先,omp parallel foromp parallelomp for的和。
  • 生成或唤醒线程的开销(许多OpenMP实现不会创建/销毁每个omp parallel)。
  • 关于omp for, (a)向工作线程调度工作负载的开销,(b)调度(特别是如果使用动态调度)。
  • 除非指定了nowait,否则omp parallel末尾的隐式屏障的开销。

供参考,为了测量OpenMP的并行开销,下面的方法会更有效:

double measureOverhead(int tripCount) {
  static const size_t TIMES = 10000;
  int sum = 0;
  int startTime = clock();
  for (size_t k = 0; k < TIMES; ++k) {
    for (int i = 0; i < tripCount; ++i) {
      sum += i;
    }
  }
  int elapsedTime = clock() - startTime;
  int startTime2 = clock();
  for (size_t k = 0; k < TIMES; ++k) {
  #pragma omp parallel for private(sum) // We don't care correctness of sum 
                                        // Otherwise, use "reduction(+: sum)"
    for (int i = 0; i < tripCount; ++i) {
      sum += i;
    }
  }
  int elapsedTime2 = clock() - startTime2;
  double parallelOverhead = double(elapsedTime2 - elapsedTime)/double(TIMES);
  return parallelOverhead;
}

尝试运行这些小代码5次,然后取平均值。此外,至少将最小的工作负载放入循环中。在上面的代码中,parallelOverhead是OpenMP的omp parallel for构造的近似开销。