我试图将一些传统的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检测到以下行之间的竞争条件:
!$OMP PARALLEL DO
(阅读)z = 3.D0
(写)Inspector“Disassembly”视图分别提供了关于这两行的以下内容(除了两行中的内存地址看起来不同之外,我对这些内容了解不多):
0x3286 callq 0x2a30 <memcpy>
0x3338 movq %r14, 0x10(%r12)
在我的主应用程序中,公共块中的一个(/ some)变量会出现问题,但对于其他以相同方式处理的变量则不会出现问题。
任何人都可以发现我的错误,或者这种竞争条件是假阳性吗?
我知道一般情况下不鼓励使用COMMON块,但我无法为当前项目更改此内容。
答案 0 :(得分:3)
从技术上讲,您的示例代码不正确,因为您使用COPYIN
初始化来自未初始化COMMON BLOCK
的数据的threadprivate副本。但这不是数据竞争的原因 - 在并行区域不会改变结果之前添加DATA
语句或只是分配给x
,y
和z
这是英特尔Fortran编译器中的一个(非常老的)错误,或者英特尔正在奇怪地解释OpenMP标准的文本(当前版本的section 2.15.4.1):
在团队成立之后以及在开始执行相关结构化块之前,复制完成,就像通过赋值一样,。
英特尔通过在概述程序的开头插入memcpy
来实施强调文本。换句话说:
!$OMP PARALLEL DO COPYIN(/firstcomm/)
do i = 1, 3000
...
end do
!$OMP END PARALLEL DO
成为(混合使用Fortran和伪代码):
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
概述的过程首先找到公共块的threadprivate副本的地址,然后将该地址与公共块本身的地址进行比较。如果两个地址都匹配,则代码正在主线程中执行,不需要复制,否则调用memcpy
将主数据的按位副本复制到threadprivate块中。
现在,人们会期望在初始化部分的末尾和循环开始之前应该有一个障碍,虽然Intel employees claim that there is one,但是没有(使用ifort 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
调用内部调度表的初始化,这可能是误报。
GCC以不同方式实施COPYIN
。它创建主线程的threadprivate数据的共享副本,然后将其复制,然后传递给工作线程,以便在复制过程中使用。