【问题标题】:What is "implicit synchronization" in OpenMP什么是 OpenMP 中的“隐式同步”
【发布时间】:2019-08-27 17:47:52
【问题描述】:

什么是 OpenMP 中的“隐式同步”,您如何发现其中之一?我的老师是这么说的

#pragma omp parallel
printf(“Hello 1\n”);

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

【问题讨论】:

    标签: parallel-processing synchronization mpi openmp barrier


    【解决方案1】:

    同步是并行处理和 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 循环并不重要。所以同步对程序的正确性没有任何帮助 添加同步屏障有两个主要缺点:

    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
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-01
      相关资源
      最近更新 更多