Fortran 为什么此openmp代码中出现分段错误?

Fortran 为什么此openmp代码中出现分段错误?,fortran,openmp,Fortran,Openmp,主程序: program main use omp_lib

主程序:

program main                                                                                                                                                    
  use omp_lib                                                                                                                                                   
  use my_module                                                                                                                                                 
  implicit none                                                                                                                                                 

  integer, parameter :: nmax = 202000                                                                                                                           
  real(8) :: e_in(nmax) = 0.D0                                                                                                                                  
  integer i                                                                                                                                                     

call omp_set_num_threads(2)                                                                                                                                     
!$omp parallel default(firstprivate)                                                                                                                            
!$omp do                                                                                                                                                        
  do i=1,2                                                                                                                                                      
     print *, e_in(i)                                                                                                                                           
     print *, eTDSE(i)                                                                                                                                          
  end do                                                                                                                                                        
!$omp end do                                                                                                                                                    
!$omp end parallel                                                                                                                                              
end program main
模块:

module my_module                                                                                                                                                
  implicit none                                                                                                                                                 

  integer, parameter, private :: ntmax = 202000                                                                                                  
  double complex :: eTDSE(ntmax) = (0.D0,0.D0)                                                                                                                  
!$omp threadprivate(eTDSE)                                                                                                                                      

end module my_module
使用以下方式编译:

ifort -openmp main.f90 my_module.f90

它给出了执行时的分段错误。如果删除主程序中的某个打印命令,则运行正常。另外,如果删除omp函数并在不使用-openmp选项的情况下编译,它也可以正常运行。

此行为最可能的原因是堆栈大小限制太小(无论出于何种原因)。由于中的
e_对每个OpenMP线程都是私有的,因此在线程堆栈上为每个线程分配一个副本(即使您指定了
-堆数组
!)<代码>202000
实数元素(种类=8)占用1616kb(或1579kib)

堆栈大小限制可以通过以下几种机制控制:

  • 在标准Unix系统外壳上,堆栈大小由
    ulimit-s
    控制。这也是主OpenMP线程的堆栈大小限制。POSIX线程(
    pthreads
    )库在创建新线程时也将此限制值用作默认线程堆栈大小

  • OpenMP支持通过环境变量
    OMP\u STACKSIZE
    控制所有附加线程的堆栈大小限制。其值是一个带有可选后缀的数字,KiB为
    k
    /
    k
    ,MiB为
    m
    /
    m
    FFO,GiB为
    g
    /
    g
    。此值不影响主线程的堆栈大小

  • GNU OpenMP运行时(
    libgomp
    )识别非标准环境变量
    GOMP\u STACKSIZE
    。如果设置,它将覆盖
    OMP\u STACKSIZE
    的值

  • 英特尔OpenMP运行时识别非标准环境变量
    KMP\u STACKSIZE
    。如果设置,则会覆盖
    OMP\u STACKSIZE
    的值,如果使用兼容OpenMP运行时(默认情况下,当前唯一可用的英特尔OpenMP运行时库是
    compat
    库),则还会覆盖
    GOMP\u STACKSIZE
    的值

  • 如果未设置任何
    *\u STACKSIZE
    变量,则在32位体系结构上英特尔OpenMP运行时的默认值为
    2m
    ,在64位体系结构上为
    4m

  • 在Windows上,主线程的堆栈大小是PE头的一部分,并由链接器嵌入其中。如果使用Microsoft的
    链接
    进行链接,则使用
    /STACK:reserve[,commit]
    指定大小。
    reserve
    参数指定以字节为单位的最大堆栈大小,而可选的
    commit
    参数指定初始提交大小。可以使用
    0x
    前缀将两者指定为十六进制值。如果不选择重新链接可执行文件,则可以通过使用编辑PE头来修改堆栈大小。它采用与链接器相同的堆栈相关参数。在启用MSVC的整个程序优化(
    /GL
    )的情况下编译的程序无法编辑

  • Win32目标的GNU链接器支持通过
    --stack
    参数设置堆栈大小。要直接从GCC传递选项,可以使用
    -Wl,--stack,

请注意,线程堆栈实际上是按照
*\u STACKSIZE
设置的大小(或默认值)分配的,这与主线程堆栈不同,主线程堆栈从小开始,然后根据需要增长到设置的限制。因此,不要将
*\u STACKSIZE
设置为任意大的值,否则可能会达到进程虚拟内存大小限制

以下是一些例子:

$ ifort -openmp my_module.f90 main.f90
将主堆栈大小限制设置为1 MiB(根据默认设置,额外的OpenMP线程将获得4 MiB):

将主堆栈大小限制设置为1700 KiB:

$ ulimit -s 1700
$ ./a.out
  0.000000000000000E+000
 (0.000000000000000E+000,0.000000000000000E+000)
  0.000000000000000E+000
 (0.000000000000000E+000,0.000000000000000E+000)
将主堆栈大小限制设置为2 MiB,将附加线程的堆栈大小设置为1 MiB:

$ ulimit -s 2048
$ KMP_STACKSIZE=1m ./a.out
zsh: segmentation fault (core dumped)  KMP_STACKSIZE=1m ./a.out
在大多数Unix系统上,主线程的堆栈大小限制由PAM或其他登录机制设置(请参见
/etc/security/limits.conf
)。Scientific Linux 6.3上的默认值是10 MiB

另一种可能导致错误的情况是虚拟地址空间限制设置得太低。例如,如果虚拟地址空间限制为1 GiB,并且线程堆栈大小限制设置为512 MiB,则OpenMP运行时将尝试为每个附加线程分配512 MiB。对于两个线程,一个线程的堆栈只有1 GiB,当代码、共享库、堆等的空间加起来时,虚拟内存大小将超过1 GiB,并且会发生错误:

将虚拟地址空间限制设置为1 GiB,并使用两个额外的线程和512 MiB堆栈运行(我已经注释掉了对
omp\u Set\u num\u threads()
)的调用):


在这种情况下,OpenMP运行时库将无法创建新线程,并在中止程序终止之前通知您。

分段故障是由于使用OpenMP时堆栈内存限制造成的。使用上一个答案中的解决方案并不能解决我在Windows操作系统上的问题。将内存分配到堆而不是堆栈内存似乎是可行的:

integer, parameter :: nmax = 202000  
real(dp), dimension(:), allocatable :: e_in
integer i

allocate(e_in(nmax))

e_in = 0

! rest of code

deallocate(e_in)
此外,这不会涉及更改任何默认环境参数


确认并参考ohm314的解决方案:

我没有注意到这一点,我认为这些阵列只是两个元素。无论如何,我经常使用-fstack数组来处理数量级更大的数组。但Ifort不是。有点令人困惑的是,无论是
gfortran
还是
Ifort
,静态数组的私有副本总是自动分配到线程堆栈上,尽管任何编译器选项都可能影响常规数组的放置。Windows应用程序的主线程堆栈大小是在链接时指定的并且不能像在大多数Unix系统上那样在以后自由控制。我的答案是Unix特有的。我将添加一个关于Windows的部分。当然,堆分配是最好的解决方案,除非不能真正实现
$ ulimit -v 1048576
$ KMP_STACKSIZE=512m OMP_NUM_THREADS=3 ./a.out
OMP: Error #34: System unable to allocate necessary resources for OMP thread:
OMP: System error #11: Resource temporarily unavailable
OMP: Hint: Try decreasing the value of OMP_NUM_THREADS.
forrtl: error (76): Abort trap signal
... trace omitted ...
zsh: abort (core dumped)  OMP_NUM_THREADS=3 KMP_STACKSIZE=512m ./a.out
integer, parameter :: nmax = 202000  
real(dp), dimension(:), allocatable :: e_in
integer i

allocate(e_in(nmax))

e_in = 0

! rest of code

deallocate(e_in)