11

我从未使用过且不知道何时使用的一个 OpenMP 指令是flush(有和没有列表)。

我有两个问题:

1.) When is an explicit `omp flush` or `omp flush(var1, ...) necessary?  
2.) Is it sometimes not necessary but helpful (i.e. can it make the code fast)?

我不明白何时使用显式刷新的主要原因是,刷新是在许多同步线程的指令(例如屏障、单一、...)之后隐式完成的。例如,我看不到使用刷新而不同步(例如 with nowait)的方式会有所帮助。

我了解不同的编译器可能omp flush以不同的方式实现。有些人可能会将带有列表的刷新解释为没有(即刷新所有共享对象)OpenMP 刷新与刷新(列表)。但我只关心规范的要求。换句话说,我想知道明确flush的原则在哪里可能是必要的或有帮助的。

编辑:我想我需要澄清我的第二个问题。让我举个例子吧。我想知道是否存在删除隐式刷新(例如使用 nowait)并改为使用显式刷新但仅在某些共享变量上会更快(并且仍然给出正确结果)的情况。类似于以下内容:

float a,b;
#pragma omp parallel
{
    #pragma omp for nowait // No barrier.  Do not flush on exit.
        //code which uses only shared variable a        
    #pragma  omp flush(a) // Flush only variable a rather than all shared variables.       
    #pragma omp for
       //Code which uses both shared variables a and b.
}

我认为在第一个 for 循环之后代码仍然需要一个屏障,但是所有的屏障都有一个隐式刷新,这样就达不到目的了。是否有可能有一个不进行冲洗的屏障?

4

2 回答 2

18

flush 指令告诉 OpenMP 编译器生成代码以使线程在共享内存上的私有视图再次保持一致。OpenMP 通常可以很好地处理这个问题,并为典型程序做正确的事情。因此,不需要flush.

但是,在某些情况下,OpenMP 编译器需要一些帮助。其中一种情况是当您尝试实现自己的自旋锁时。在这些情况下,您需要组合刷新才能使事情正常进行,否则自旋变量将不会更新。获得正确的冲洗顺序将是困难的,而且非常非常容易出错。

一般建议是不应使用冲洗。如果有的话,程序员应该尽量避免使用列表 ( ) 刷新flush(var,...)。有些人实际上是在谈论在未来的 OpenMP 中弃用它。

就性能而言,刷新的影响应该是负面多于正面。由于它会导致编译器生成内存栅栏和额外的加载/存储操作,我希望它会减慢速度。

编辑:对于你的第二个问题,答案是否定的。OpenMP 确保每个线程在需要时对共享内存具有一致的视图。如果线程不同步,他们不需要更新他们对共享内存的看法,因为他们看不到那里有任何“有趣”的变化。这意味着线程进行的任何读取都不会读取已被其他线程更改的任何数据。如果是这种情况,那么您的程序中就会出现竞争条件和潜在的错误。为了避免竞争,您需要同步(这意味着刷新以使每个参与线程的视图再次一致)。类似的论点适用于障碍。在并行区域的计算中,您使用障碍开始一个新纪元。由于您使线程保持同步,

顺便说一句,OpenMP可能会为线程保留私有数据,但并非必须如此。因此,OpenMP 编译器很可能会将变量保留在寄存器中一段时间​​,这会导致它们与共享内存不同步。然而,对数组元素的更新通常很快就会反映在共享内存中,因为线程的私有存储量通常很小(寄存器集、缓存、暂存内存等)。OpenMP 仅对您可以预期的内容提供一些较弱的限制。实际的 OpenMP 实现(或硬件)可能与它希望的一样严格(例如,立即写回任何更改并始终刷新)。

于 2013-10-30T22:53:50.687 回答
0

不完全是答案,但 Michael Klemm 的问题已关闭以供评论。我认为为什么刷新如此难以理解和正确使用的一个很好的例子是从OpenMP 示例中复制(并缩短了一点)以下示例

//http://www.openmp.org/wp-content/uploads/openmp-examples-4.0.2.pdf
//Example mem_model.2c, from Chapter 2 (The OpenMP Memory Model)
int main() {
   int data, flag = 0;
   #pragma omp parallel num_threads(2)
   {
      if (omp_get_thread_num()==0) {
         /* Write to the data buffer that will be read by thread */
         data = 42;
         /* Flush data to thread 1 and strictly order the write to data
            relative to the write to the flag */
         #pragma omp flush(flag, data)
         /* Set flag to release thread 1 */
         flag = 1;
         /* Flush flag to ensure that thread 1 sees S-21 the change */
         #pragma omp flush(flag)
      }
      else if (omp_get_thread_num()==1) {
         /* Loop until we see the update to the flag */
         #pragma omp flush(flag, data)
         while (flag < 1) {
            #pragma omp flush(flag, data)
         }
         /* Values of flag and data are undefined */
         printf("flag=%d data=%d\n", flag, data);
         #pragma omp flush(flag, data)
         /* Values data will be 42, value of flag still undefined */
         printf("flag=%d data=%d\n", flag, data);
      }
   }
   return 0;
}

阅读评论并尝试理解。

于 2017-02-23T10:05:53.203 回答