【发布时间】:2019-08-27 17:47:52
【问题描述】:
什么是 OpenMP 中的“隐式同步”,您如何发现其中之一?我的老师是这么说的
#pragma omp parallel
printf(“Hello 1\n”);
具有隐式同步。为什么?你怎么看?
【问题讨论】:
标签: parallel-processing synchronization mpi openmp barrier
什么是 OpenMP 中的“隐式同步”,您如何发现其中之一?我的老师是这么说的
#pragma omp parallel
printf(“Hello 1\n”);
具有隐式同步。为什么?你怎么看?
【问题讨论】:
标签: parallel-processing synchronization mpi openmp barrier
同步是并行处理和 openmp 中的一个重要问题。通常并行处理是异步的。您知道有多个线程正在处理一个问题,但您无法确切知道它们的实际状态、它们正在进行的迭代等。同步允许您控制线程执行。
openmp 中有两种同步方式:显式和隐式。显式同步是使用允许创建 barrier 的特定 openmp 构造完成的:#pragma omp barrier。屏障是一种并行结构,只能由所有线程同时传递。因此,在屏障之后,您可以准确地知道所有线程的状态,更重要的是,它们完成了多少工作。
隐式同步在两种情况下完成:
在平行区域的末端。 Openmp 依赖于 fork-join 模型。当程序启动时,会创建一个线程(主线程)。当您通过#pragma omp parallel 创建并行部分时,会创建多个线程(fork)。这些线程将同时工作,并在并行部分结束时被销毁(join)。因此,在并行部分结束时,您会进行同步,并且您可以准确地知道所有线程的状态(它们已完成工作)。这就是您给出的示例中发生的情况。并行部分仅包含printf(),最后,程序等待所有线程终止后再继续。
在诸如#pragma omp for 或#pragma omp sections 之类的一些openmp 结构的末尾,有一个隐含的障碍。只要所有线程都没有到达屏障,就没有线程可以继续工作。准确了解不同线程所做的工作非常重要。
例如,考虑以下代码。
#pragma omp parallel
{
#pragma omp for
for(int i=0; i<N; i++)
A[i]=f(i); // compute values for A
#pragma omp for
for(int j=0; j<N/2; j++)
B[j]=A[j]+A[j+N/2];// use the previously computed vector A
} // end of parallel section
由于所有线程都是异步工作的,因此您不知道哪些线程已完成创建其向量A 的一部分。如果没有同步,线程可能会快速完成第一个for 循环的部分,进入第二个for 循环并访问向量A 的元素,而应该计算它们的线程仍在第一个循环并没有计算出A[i]的对应值。
这就是为什么 openmp 编译器添加一个隐式屏障来同步所有线程的原因。因此,您可以确定所有线程都已完成所有工作,并且当第二个 for 循环开始时,A 的所有值都已计算完毕。
但在某些情况下,不需要同步。例如,考虑以下代码:
#pragma omp parallel
{
#pragma omp for
for(int i=0; i<N; i++)
A[i]=f(i); // compute values for A
#pragma omp for
for(int j=0; j<N/2; j++)
B[j]=g(j);// compute values for B
} // end of parallel section
显然这两个循环是完全独立的,如果A 被正确计算以启动第二个for 循环并不重要。所以同步对程序的正确性没有任何帮助
添加同步屏障有两个主要缺点:
如果函数f() 的运行时间相差很大,您可能有一些线程已完成工作,而其他线程仍在计算。同步将强制之前的线程等待,而这种空闲状态并不能正确利用并行性。
同步成本很高。实现屏障的一种简单方法是在到达屏障时增加一个全局计数器,并等待计数器的值等于线程数omp_get_num_threads()。为了避免线程之间的竞争,全局计数器的递增必须使用需要大量周期的原子读取-修改-写入来完成,并且等待计数器的正确值通常使用浪费处理器的自旋锁来完成循环。
因此存在抑制隐式同步的构造,而对前一个循环进行编程的最佳方法是:
#pragma omp parallel
{
#pragma omp for nowait // nowait suppresses implicit synchronisations.
for(int i=0; i<N; i++)
A[i]=f(i); // compute values for A
#pragma omp for
for(int j=0; j<N/2; j++)
B[j]=g(j);// compute values for B
} // end of parallel section
这样,一旦线程在第一个循环中完成了它的工作,它就会立即开始处理第二个for 循环,并且根据实际程序,这可能会大大减少执行时间。
【讨论】: