启用 OpenMP 时英特尔 Fortran 字符指针损坏
Intel Fortran character pointer corruption when enabling OpenMP
我正在为相当大的代码(天气预报模型)研究 Intel Fortran 兼容性。在 Intel Fortran(和 only Intel Fortran)上,一些字符数据似乎被扰乱循环并指向它的字符指针。我最终得到的字符只包含 0 和 9。字符串 c
的内容只有 "00:00:00:16 (whitespace padding....)"
。
每当我包含任何(死的)OpenMP 代码和 使用-fopenmp
编译器标志时,我都能重现出现的问题。我对这里发生的事情有点迷茫。你能检测出以下最小复制器中的任何编程错误吗?如果没有,我想我会提交一个编译器错误。
复制者
minimal.f90
module foo
implicit none
type pointer_character
character, pointer :: c
end type
type(pointer_character), allocatable :: ptr_c(:)
integer(4) :: idx_c
contains
subroutine grpbcast_set_c(c)
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
allocate(ptr_c( 256 ))
idx_c = 0
do cloc = 1, len(c)
idx_c = idx_c + 1
ptr_c( idx_c )%c => c( cloc:cloc )
end do
print *, "testprint-2", c
print *, "testprint-3.1", ptr_c( 1 )%c
print *, "testprint-3.2", ptr_c( 2 )%c
print *, "testprint-3.3", ptr_c( 3 )%c
print *, "testprint-3.4", ptr_c( 11 )%c
end subroutine
! The following subroutine is never called, but if we include it in the module foo, it will lead to the data corruption documented in
!
! If the subroutine is commented it will work
!
! In other words:
! if we comment this subroutine out, the output will be
! =====================================================
! testprint-2
! 00:00:00:16
! testprint-3.10
! testprint-3.20
! testprint-3.3:
! testprint-3.46
! =====================================================
! However if we include it, the output will be
! testprint-2
! 00:00:00:16
! testprint-3.10
! testprint-3.20
! testprint-3.30
! testprint-3.40
! =====================================================
!
! tested with: ifort 17.0.1 20161005
subroutine scatter_one_record()
implicit none
integer(4) :: k
!$OMP PARALLEL DO
do k = 1, 5
end do
!$OMP END PARALLEL DO
end subroutine
end module foo
program main
use foo, only: grpbcast_set_c
implicit none
character(len=256), target:: run_period
run_period = "00:00:00:16"
call grpbcast_set_c(run_period)
end program
生成文件
.PHONY: all
all: minimal
minimal.o: minimal.f90
ifort -fopenmp -c minimal.f90 -o minimal.o
minimal: minimal.o
ifort -fopenmp -o minimal -L./ minimal.o
ifort版本
> ifort --version
ifort (IFORT) 17.0.1 20161005
编译&运行
make
./minimal
..
..
前面的分析,到这里才明白这道题的评论链
有问题的循环
subroutine grpbcast_set_c(c)
use nrtype, only : rp => rp
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
do cloc = 1, len(c)
idx_c = idx_c + 1
if (idx_c > max_ptr_gbcast) stop 9
ptr_c( idx_c )%c => c( cloc:cloc )
end do
end subroutine
模块ptr_c的规范
type pointer_character
character, pointer :: c
end type
type(pointer_character), private, allocatable, save :: ptr_c(:)
在调试器 (TotalView) 中看到的数据
编译命令
> mpif90 -O0 -no-ipo -g -convert big_endian -fopenmp -r8 -DUSE_MPI -I/usr/apps.sp3/mpi/openmpi/1.6.5/i2013.1.046/include -I/usr/apps.sp3/mpi/openmpi/1.6.5/i2013.1.046/lib -I [NUSDAS13_PATH]/src -I [NETCDF_PATH]/include -DUSE_MPI -c mpi_comm.f90 -o mpi_comm.o
> mpif90 --version
ifort (IFORT) 14.0.2 20140120
Copyright (C) 1985-2014 Intel Corporation. All rights reserved.
更新
我刚刚用最新的英特尔编译器版本尝试了同样的方法:
> mpif90 --version
> ifort (IFORT) 17.0.1 20161005
结果还是错了但是不同!这次我把所有字符归零了。
更新 2
我一直在尝试用最小的复制器重现这个问题(见下文),到目前为止运气不好,即当 运行 以这种方式出现时,错误没有出现。然而,有一件事很有趣:TotalView 显示数组的首地址略有不同:
我想一定是输入 c
有问题,尽管它在调试器中正确显示了输入 grpcast_set_c
的位置。
module foo
implicit none
type pointer_character
character, pointer :: c
end type
type(pointer_character), allocatable, save :: ptr_c(:)
integer(4), save :: idx_c
contains
subroutine parmread_bcast_a( mt, aout, chktag )
implicit none
integer(4), intent(in):: mt
character(*), intent(out):: aout
character(*), intent(in):: chktag
call filetool_cardread_a( mt, aout, chktag )
write(6,'("* ",a28," = ", a)') chktag, trim(aout)
call set_ptr_c(aout)
return
end subroutine parmread_bcast_a
subroutine filetool_cardread_a( imt, aout, chktag )
implicit none
integer(4), intent(in):: imt
character(*), intent(out):: aout
character(*), intent(in), optional:: chktag
character(len=256):: tag
character(len=256):: val
character(len=256):: buff
integer(4):: idxeql
integer(4) :: is_debug = 0
cardread: do
read( imt, "(a)" ) buff
if( buff(1:1) /= "*" ) then
idxeql = index(buff, "=")
tag = adjustl(buff(:idxeql-1))
if (is_debug == 1) then
write(6,*) 'buff', trim(buff)
endif
if ( present( chktag ) ) then
if ( trim(chktag) /= trim(tag) ) then
write(6,*)"WARNING !!! the record is for ", trim(tag), &
& ", while you specified ", trim(chktag)
cycle cardread
endif
endif
val = adjustl(buff(idxeql+1:))
aout = val
return
end if
end do cardread
end subroutine filetool_cardread_a
subroutine set_ptr_c(c)
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
do cloc = 1, len(c)
idx_c = idx_c + 1
if (idx_c > 10000) stop 9
ptr_c( idx_c )%c => c( cloc:cloc )
end do
end subroutine
end module
program main
use foo, only: ptr_c, idx_c, parmread_bcast_a
implicit none
character(len=256), save:: run_period
allocate(ptr_c( 10000 ))
idx_c = 0
open( 100, file='sample.conf', form='formatted')
call parmread_bcast_a(100, run_period, "run_period")
print *, "run period", run_period
print *, ptr_c( 1 )%c
print *, ptr_c( 2 )%c
print *, ptr_c( 3 )%c
print *, ptr_c( 11 )%c
end program
sample.conf
run_period = 00:00:00:16
作为解决方法,将 OpenMP 代码从这种数据结构初始化中分离到多个模块(即使在同一文件中)是可行的。在上面的例子中:
module foo1
implicit none
type pointer_character
character, pointer :: c
end type
type(pointer_character), allocatable :: ptr_c(:)
integer(4) :: idx_c
contains
subroutine grpbcast_set_c(c)
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
allocate(ptr_c( 256 ))
idx_c = 0
do cloc = 1, len(c)
idx_c = idx_c + 1
ptr_c( idx_c )%c => c( cloc:cloc )
end do
print *, "testprint-2", c
print *, "testprint-3.1", ptr_c( 1 )%c
print *, "testprint-3.2", ptr_c( 2 )%c
print *, "testprint-3.3", ptr_c( 3 )%c
print *, "testprint-3.4", ptr_c( 11 )%c
end subroutine
end module
module foo2
implicit none
contains
subroutine scatter_one_record()
implicit none
integer(4) :: k
!$OMP PARALLEL DO
do k = 1, 5
end do
!$OMP END PARALLEL DO
end subroutine
end module
program main
use foo1, only: grpbcast_set_c
implicit none
character(len=256), target:: run_period
run_period = "00:00:00:16"
call grpbcast_set_c(run_period)
end program
我正在为相当大的代码(天气预报模型)研究 Intel Fortran 兼容性。在 Intel Fortran(和 only Intel Fortran)上,一些字符数据似乎被扰乱循环并指向它的字符指针。我最终得到的字符只包含 0 和 9。字符串 c
的内容只有 "00:00:00:16 (whitespace padding....)"
。
每当我包含任何(死的)OpenMP 代码和 使用-fopenmp
编译器标志时,我都能重现出现的问题。我对这里发生的事情有点迷茫。你能检测出以下最小复制器中的任何编程错误吗?如果没有,我想我会提交一个编译器错误。
复制者
minimal.f90
module foo
implicit none
type pointer_character
character, pointer :: c
end type
type(pointer_character), allocatable :: ptr_c(:)
integer(4) :: idx_c
contains
subroutine grpbcast_set_c(c)
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
allocate(ptr_c( 256 ))
idx_c = 0
do cloc = 1, len(c)
idx_c = idx_c + 1
ptr_c( idx_c )%c => c( cloc:cloc )
end do
print *, "testprint-2", c
print *, "testprint-3.1", ptr_c( 1 )%c
print *, "testprint-3.2", ptr_c( 2 )%c
print *, "testprint-3.3", ptr_c( 3 )%c
print *, "testprint-3.4", ptr_c( 11 )%c
end subroutine
! The following subroutine is never called, but if we include it in the module foo, it will lead to the data corruption documented in
!
! If the subroutine is commented it will work
!
! In other words:
! if we comment this subroutine out, the output will be
! =====================================================
! testprint-2
! 00:00:00:16
! testprint-3.10
! testprint-3.20
! testprint-3.3:
! testprint-3.46
! =====================================================
! However if we include it, the output will be
! testprint-2
! 00:00:00:16
! testprint-3.10
! testprint-3.20
! testprint-3.30
! testprint-3.40
! =====================================================
!
! tested with: ifort 17.0.1 20161005
subroutine scatter_one_record()
implicit none
integer(4) :: k
!$OMP PARALLEL DO
do k = 1, 5
end do
!$OMP END PARALLEL DO
end subroutine
end module foo
program main
use foo, only: grpbcast_set_c
implicit none
character(len=256), target:: run_period
run_period = "00:00:00:16"
call grpbcast_set_c(run_period)
end program
生成文件
.PHONY: all
all: minimal
minimal.o: minimal.f90
ifort -fopenmp -c minimal.f90 -o minimal.o
minimal: minimal.o
ifort -fopenmp -o minimal -L./ minimal.o
ifort版本
> ifort --version
ifort (IFORT) 17.0.1 20161005
编译&运行
make
./minimal
..
..
前面的分析,到这里才明白这道题的评论链
有问题的循环
subroutine grpbcast_set_c(c)
use nrtype, only : rp => rp
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
do cloc = 1, len(c)
idx_c = idx_c + 1
if (idx_c > max_ptr_gbcast) stop 9
ptr_c( idx_c )%c => c( cloc:cloc )
end do
end subroutine
模块ptr_c的规范
type pointer_character
character, pointer :: c
end type
type(pointer_character), private, allocatable, save :: ptr_c(:)
在调试器 (TotalView) 中看到的数据
编译命令
> mpif90 -O0 -no-ipo -g -convert big_endian -fopenmp -r8 -DUSE_MPI -I/usr/apps.sp3/mpi/openmpi/1.6.5/i2013.1.046/include -I/usr/apps.sp3/mpi/openmpi/1.6.5/i2013.1.046/lib -I [NUSDAS13_PATH]/src -I [NETCDF_PATH]/include -DUSE_MPI -c mpi_comm.f90 -o mpi_comm.o
> mpif90 --version
ifort (IFORT) 14.0.2 20140120
Copyright (C) 1985-2014 Intel Corporation. All rights reserved.
更新
我刚刚用最新的英特尔编译器版本尝试了同样的方法:
> mpif90 --version
> ifort (IFORT) 17.0.1 20161005
结果还是错了但是不同!这次我把所有字符归零了。
更新 2
我一直在尝试用最小的复制器重现这个问题(见下文),到目前为止运气不好,即当 运行 以这种方式出现时,错误没有出现。然而,有一件事很有趣:TotalView 显示数组的首地址略有不同:
我想一定是输入 c
有问题,尽管它在调试器中正确显示了输入 grpcast_set_c
的位置。
module foo
implicit none
type pointer_character
character, pointer :: c
end type
type(pointer_character), allocatable, save :: ptr_c(:)
integer(4), save :: idx_c
contains
subroutine parmread_bcast_a( mt, aout, chktag )
implicit none
integer(4), intent(in):: mt
character(*), intent(out):: aout
character(*), intent(in):: chktag
call filetool_cardread_a( mt, aout, chktag )
write(6,'("* ",a28," = ", a)') chktag, trim(aout)
call set_ptr_c(aout)
return
end subroutine parmread_bcast_a
subroutine filetool_cardread_a( imt, aout, chktag )
implicit none
integer(4), intent(in):: imt
character(*), intent(out):: aout
character(*), intent(in), optional:: chktag
character(len=256):: tag
character(len=256):: val
character(len=256):: buff
integer(4):: idxeql
integer(4) :: is_debug = 0
cardread: do
read( imt, "(a)" ) buff
if( buff(1:1) /= "*" ) then
idxeql = index(buff, "=")
tag = adjustl(buff(:idxeql-1))
if (is_debug == 1) then
write(6,*) 'buff', trim(buff)
endif
if ( present( chktag ) ) then
if ( trim(chktag) /= trim(tag) ) then
write(6,*)"WARNING !!! the record is for ", trim(tag), &
& ", while you specified ", trim(chktag)
cycle cardread
endif
endif
val = adjustl(buff(idxeql+1:))
aout = val
return
end if
end do cardread
end subroutine filetool_cardread_a
subroutine set_ptr_c(c)
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
do cloc = 1, len(c)
idx_c = idx_c + 1
if (idx_c > 10000) stop 9
ptr_c( idx_c )%c => c( cloc:cloc )
end do
end subroutine
end module
program main
use foo, only: ptr_c, idx_c, parmread_bcast_a
implicit none
character(len=256), save:: run_period
allocate(ptr_c( 10000 ))
idx_c = 0
open( 100, file='sample.conf', form='formatted')
call parmread_bcast_a(100, run_period, "run_period")
print *, "run period", run_period
print *, ptr_c( 1 )%c
print *, ptr_c( 2 )%c
print *, ptr_c( 3 )%c
print *, ptr_c( 11 )%c
end program
sample.conf
run_period = 00:00:00:16
作为解决方法,将 OpenMP 代码从这种数据结构初始化中分离到多个模块(即使在同一文件中)是可行的。在上面的例子中:
module foo1
implicit none
type pointer_character
character, pointer :: c
end type
type(pointer_character), allocatable :: ptr_c(:)
integer(4) :: idx_c
contains
subroutine grpbcast_set_c(c)
implicit none
character(*), intent(in), target :: c
integer(4) :: cloc
allocate(ptr_c( 256 ))
idx_c = 0
do cloc = 1, len(c)
idx_c = idx_c + 1
ptr_c( idx_c )%c => c( cloc:cloc )
end do
print *, "testprint-2", c
print *, "testprint-3.1", ptr_c( 1 )%c
print *, "testprint-3.2", ptr_c( 2 )%c
print *, "testprint-3.3", ptr_c( 3 )%c
print *, "testprint-3.4", ptr_c( 11 )%c
end subroutine
end module
module foo2
implicit none
contains
subroutine scatter_one_record()
implicit none
integer(4) :: k
!$OMP PARALLEL DO
do k = 1, 5
end do
!$OMP END PARALLEL DO
end subroutine
end module
program main
use foo1, only: grpbcast_set_c
implicit none
character(len=256), target:: run_period
run_period = "00:00:00:16"
call grpbcast_set_c(run_period)
end program