什么是OpenMP中的“隐式同步”

ali*_*low 2 parallel-processing synchronization mpi openmp barrier

OpenMP中到底是什么“隐式同步”,如何发现它?我老师说

#pragma omp parallel
printf(“Hello 1\n”);
Run Code Online (Sandbox Code Playgroud)

具有隐式同步。为什么?你怎么看?

Ala*_*got 5

同步是并行处理和openmp中的重要问题。通常,并行处理是异步的。您知道有几个线程正在处理一个问题,但是您无法确切知道它们的实际状态,正在处理的迭代等。同步使您可以控制线程的执行。

openmp中有两种同步:显式和隐式。一个明确的同步是通过一个特定的openmp构造完成的,它允许创建一个屏障#pragma omp barrier。屏障是并行构造,只能同时由所有线程传递。因此,在遇到障碍之后,您将确切地知道所有线程的状态,更重要的是,他们知道它们已经完成了多少工作。

隐式同步在两种情况下完成:

  • 在平行区域的末端。Openmp依赖于fork-join模型。程序启动时,将创建一个线程(主线程)。当您通过创建并行段时#pragma omp parallel,将创建多个线程(fork)。这些线程将同时工作,并且在并行部分的末尾将被销毁(join)。因此,在并行部分的最后,您进行了同步,并且您确切地知道了所有线程的状态(它们已经完成了工作)。这就是您提供的示例中发生的情况。并行部分仅包含printf()和,最后,程序在继续之前等待所有线程的终止。

  • 在某些openmp构造(#pragma omp for或)的末尾#pragma omp sections有一个隐式障碍。只要所有线程都未达到屏障,任何线程都无法继续工作。准确地了解不同线程已完成的工作很重要。

例如,考虑以下代码。

#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
Run Code Online (Sandbox Code Playgroud)

由于所有线程都是异步工作的,因此您不知道哪个线程已完成创建vector的一部分A。如果没有同步,则存在线程快速完成其在第一for循环中的部分,进入第二for循环并访问vector元素的风险,A而应该计算它们的线程仍在第一循环中,并且尚未计算相应的的价值A[i]

这就是openmp编译器添加隐式屏障以同步所有线程的原因。因此,可以确定所有线程都已完成所有工作,并且A在第二个for循环开始时已计算出所有的值。

但是在某些情况下,不需要同步。例如,考虑以下代码:

#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
Run Code Online (Sandbox Code Playgroud)

显然,这两个循环是完全独立的,并且是否A经过适当计算以启动第二个for循环并不重要。因此,同步对于程序的正确性没有任何帮助,并且添加同步障碍有两个主要缺点:

  1. 如果函数的f()运行时间有很大不同,则您可能有一些线程已经完成工作,而其他线程仍在计算。同步将迫使先前的线程等待,并且这种空闲状态无法正确利用并行性。

  2. 同步很昂贵。实现屏障的一种简单方法是在达到屏障时增加全局计数器,并等待直到计数器的值等于线程数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
Run Code Online (Sandbox Code Playgroud)

这样,线程在第一个循环中完成工作后,将立即开始处理第二个for循环,并且根据实际程序,这可能会大大减少执行时间。