OpenMP 竞争条件(带 COMMON 块的 Fortran 77)

OpenMP race condition (Fortran 77 w/ COMMON block)

我正在尝试将一些遗留 Fortran 代码与 OpenMP 并行化。 使用 Intel Inspector 检查竞争条件,我在以下代码中遇到了问题(经过简化的测试示例):

        PROGRAM TEST

!$      use omp_lib
        implicit none
        DOUBLE PRECISION :: x,y,z
        COMMON /firstcomm/ x,y,z
!$OMP THREADPRIVATE(/firstcomm/)

        INTEGER :: i
!$      call omp_set_num_threads(3)       

!$OMP PARALLEL DO
!$OMP+ COPYIN(/firstcomm/)
!$OMP+ PRIVATE(i)
        do i=1,3000
            z = 3.D0
            y = z+log10(z)
            x=y+z
        enddo
!$OMP END PARALLEL DO

        END PROGRAM TEST

Intel Inspector 检测到以下行之间的竞争条件:

Inspector "Disassembly" 视图分别提供了以下关于这两行的内容(我对这些不太了解,除了两行中的内存地址似乎不同之外):

在我的主要应用程序中,公共块中的一个(/某些)变量会出现问题,但其他以相同方式处理的变量不会出现问题。

谁能发现我的错误,或者这个竞争条件是误报吗?

我知道通常不鼓励使用 COMMON 块,但我无法针对当前项目更改此设置。

从技术上讲,您的示例代码是不正确的,因为您正在使用 COPYIN 使用来自未初始化 COMMON BLOCK 的数据初始化线程私有副本。但这不是数据竞争的原因——在并行区域之前添加 DATA 语句或简单地分配给 xyz 不会改变结果.

这要么是英特尔 Fortran 编译器中的一个(非常古老的)错误,要么是英特尔对 OpenMP 标准(当前版本的section 2.15.4.1)文本的解释很奇怪:

The copy is done, as if by assignment, after the team is formed and prior to the start of execution of the associated structured block.

英特尔通过在概述过程的开头插入 memcpy 来实现强调文本。换句话说:

!$OMP PARALLEL DO COPYIN(/firstcomm/)
do i = 1, 3000
   ...
end do
!$OMP END PARALLEL DO

变成(在 Fortran 和 pseudo-code 的混合体中):

par_region0:
   my_firstcomm = get_threadprivate_copy(/firstcomm/)
   if (my_firstcomm != firstcomm) then
      memcpy(my_firstcomm, firstcomm, size of firstcomm)
   end if
   // Actual implementation of the DO worksharing construct
   call determine_iterations(1, 3000, low_it, high_it)
   do i = low_it, high_it
     ...
     ... my_firstcomm used here instead of firstcomm
     ...
   end do
   call openmp_barrier
end par_region0

MAIN:
   // Prepare a parallel region with 3 threads
   // and fire the outlined code in the worker threads
   call start_parallel_region(3, par_region0)
   // Fire the outlined code in the master thread
   call par_region0
   call end_parallel_region

概述的过程首先找到公共块的线程私有副本的地址,然后将该地址与公共块本身的地址进行比较。如果两个地址匹配,则代码正在主线程中执行,不需要复制,否则调用 memcpy 将主数据按位复制到 threadprivate 块中。

现在,人们会期望在初始化部分的末尾和循环开始之前应该有一个屏障,虽然 Intel employees claim that there is one,但有 none(用11.0、14.0 和 16.0)。更重要的是,英特尔 Fortran 编译器不遵守 COPYIN 子句中的变量列表,如果其中包含的任何变量在子句中列出,即复制整个公共块,即 COPYIN(x) 被视为相同作为 COPYIN(/firstcomm/).

无论这些是英特尔 Fortran 编译器的错误还是功能,只有英特尔可以判断。也可能是我误读了汇编输出。 如果有人能找到缺少的障碍,请告诉我。一种可能的解决方法是拆分组合指令并在工作共享构造之前插入一个显式障碍:

!$OMP PARALLEL COPYIN(/firstcomm/) PRIVATE(I)
!$OMP BARRIER
!$OMP DO
      do i = 1, 3000
         z = 3.D0
         y = z+log10(z)
         x = y+z
      end do
!$OMP END DO
!$OMP END PARALLEL

随着这一变化,数据竞争将转移到 log10 调用中内部调度 table 的初始化,这可能是误报。

GCC 以不同的方式实现 COPYIN。它创建主线程的 threadprivate 数据的共享副本,然后将其复制到工作线程以供复制过程使用。