从使用 OpenMP 的教程中计算 Pi 算法

Posted

技术标签:

【中文标题】从使用 OpenMP 的教程中计算 Pi 算法【英文标题】:Calculate Pi algorithm from a tutorial using OpenMP 【发布时间】:2018-11-11 01:01:42 【问题描述】:

我正在学习关于 OpenMP 的 this 教程,我在第 19 页遇到了这个练习。这是一个我必须并行化的 pi 计算算法:

static long num_steps = 100000;
double step;
void main ()

  int i;
  double x, pi
  double sum = 0.0;
  step = 1.0 / (double)num_steps;

  for(i = 0; i < num_steps; i++)
  
     x = (I + 0.5) * step;
     sum = sum + 4.0 / (1.0 + x*x);
  

  pi = step * sum;

到目前为止,我无法使用#pragma parallel for。我只能使用:

#pragma omp parallel 
omp_get_thread_num();
omp_set_num_threads(int);
omp_get_num_threads();

我的实现如下所示:

#define NUM_STEPS 800

int main(int argc, char **argv)

   int num_steps = NUM_STEPS;
   int i;
  double x;
  double pi;
  double step = 1.0 / (double)num_steps;

  double sum[num_steps];

  for(i = 0; i < num_steps; i++)
  
      sum[i] = 0;
  

  omp_set_num_threads(num_steps);
  #pragma omp parallel
  
    x = (omp_get_thread_num() + 0.5) * step;
    sum[omp_get_thread_num()] += 4.0 / (1.0 + x * x);
  

  double totalSum = 0;

  for(i = 0; i < num_steps; i++)
  
    totalSum += sum[i];
  

  pi = step * totalSum;

  printf("Pi: %.5f", pi);

通过使用 sum 数组忽略问题(稍后解释它需要使用 #pragma omp critical 或 #pragma omp atomic 为 sum 值定义临界区),上述实施仅适用于有限数量的线程(在我的情况下为 800),其中串行代码使用 100000 步。有没有办法只使用前面提到的 OpenMP 命令来实现这一点,还是我必须使用 #pragma omp parallel for,这在教程中还没有提到?

非常感谢您抽出宝贵时间,我真的很想通过 OpenMP 掌握 C 语言中的并行化概念。

【问题讨论】:

你可以使用#pragma omp atomic吗? @IncreasinglyIdiotic 后面会解释#pragma omp atomic 的用处,但仅涉及和值。如何使用它来解决“线程过多”的问题?有没有办法,不用parallel for? 本教程定期将困惑的学习者发送到 ***。我建议您寻找遵循更惯用的高级方法的学习材料,而不是自下而上地解释 OpenMP。如果您正在参加现场研讨会,也许它会起作用,但在在线阅读/观看材料时肯定不会。 @Zulan [ad]***:自 2008 年以来将混乱转化为知识:-D[/ad] 【参考方案1】:

您需要找到一种方法,使您的并行算法在某种程度上独立于线程数。

最简单的方法是这样做:

int tid = omp_get_thread_num();
int n_threads = omp_get_num_threads();

for (int i = tid; i < num_steps; i += n_threads) 
    // ...

这样,无论线程数如何,工作都会被分配到所有线程中。

如果有 3 个线程和 9 个步骤:

线程 0 将执行步骤 0、3、6 线程 1 将执行步骤 1、4、7 线程 2 将执行步骤 2、5、8

这可行,但如果每个线程都从某个共享数组访问数据,这并不理想。如果线程出于locality 的目的访问附近的数据部分会更好。

在这种情况下,您可以将步骤数除以线程数,并为每个线程分配一组连续的任务,如下所示:

int tid = omp_get_thread_num();
int n_threads = omp_get_num_threads();

int steps_per_thread = num_steps / n_threads;
int start = tid * steps_per_thread;
int end = start + steps_per_thread;

for (int i = start; i < end; i++) 
    // ...

现在执行 9 个步骤的 3 个线程看起来像:

线程 0 执行步骤 0、1、2 线程 1 执行步骤 3、4、5 线程 2 执行步骤 6、7、8

这种方法实际上是使用#pragma omp for 时最有可能发生的情况。在大多数情况下,编译器只是根据线程的数量来划分任务,并为每个线程分配一个部分。

因此,给定一组 2 个线程和 100 次循环迭代,编译器可能会将迭代 0-49 分配给线程 0,将迭代 50-99 分配给线程 1。

请注意,如果迭代次数不能除以线程数,则需要显式处理余数。

【讨论】:

谢谢,这是一个非常有用的答案,因为它可以帮助我阐明并行化的“内部工作原理”与#pragma omp parallel for 等功能有关。

以上是关于从使用 OpenMP 的教程中计算 Pi 算法的主要内容,如果未能解决你的问题,请参考以下文章

MPI和OpenMP混合编程计算pi π值

使用非线程安全随机数生成器在 C 中为 pi monte carlo 更正 OpenMP 编译指示

增加线程数减少时间

OpenMP使用体验报告(概述)

如何为Fibonacci算法设计并行代码,但没有openmp任务?

OpenMP的简单使用教程