10

我在 Fortran 90 中编写了一个相当大的程序。它已经运行了很长一段时间,但是今天我试图将它提高一个档次并增加问题的大小(它是一个研究非标准 FE 求解器,如果帮助任何人...)现在我收到“堆栈溢出”错误消息,程序自然会终止,而没有给我任何有用的东西。

该程序首先设置所有相关的数组和矩阵,然后将几行有关此的统计信息打印到日志文件中。即使有我的新的更大的问题,这也可以正常工作(尽管有点慢),但是随着“数字运算”的进行,它会失败。

让我感到困惑的是,那时所有的东西都已经分配好了(并且工作没有错误)。我不完全确定堆栈是什么(维基百科和这里的几个步骤并没有做太多,因为我对计算机的“幕后”工作只有相当基本的了解)。

假设我有一些数组初始化为:

INTEGER,DIMENSION(64) :: IA
REAL(8),DIMENSION(:,:),ALLOCATABLE :: AA, BB

在一些初始化例程(即从文件中读取输入等)之后被分配为(我存储了一些大小整数以便更容易地传递给固定大小的 IA 中的子例程):

ALLOCATE( AA(N1,N2) , BB(N1,N2) )
IA(1) = N1
IA(2) = N2

这基本上是在初始部分发生的事情,到目前为止一切都很好。但是当我然后调用一个子程序时

CALL ROUTINE_ONE(AA,BB,IA)

例程看起来像(没什么花哨的):

SUBROUTINE ROUTINE_ONE(AA,BB,IA)
IMPLICIT NONE
INTEGER,DIMENSION(64) :: IA
REAL(8),DIMENSION(IA(1),IA(2)) :: AA, BB
...
do lots of other stuff
...
END SUBROUTINE ROUTINE_ONE

现在我得到一个错误!屏幕输出显示:

forrtl: severe (170): Program Exception - stack overflow

但是,当我使用调试器运行程序时,它会在名为winsig.c(不是我的文件,但可能是编译器的一部分?)的文件中的第 419 行中断。它似乎是调用例程的一部分,sigreterror:它是已调用的默认情况,返回 text Invalid signal or error。附有一条注释行,奇怪的是/* should never happen, but compiler can't tell */……?

所以我想我的问题是,为什么会发生这种情况以及实际发生了什么?我以为只要我可以分配所有相关的内存就可以了?对子例程的调用是否会复制参数,或者只是指向它们的指针?如果答案是副本,那么我可以看到问题可能出在哪里,如果是这样:关于如何解决它的任何想法?

我试图解决的问题很大,但绝不是疯狂的。标准有限元求解器可以处理比我目前的更大的问题。我在 Dell PowerEdge 1850 上运行程序,操作系统是 Microsoft Server 2008 R2 Enterprise。根据提示systeminfocmd我有 8GB 的​​物理内存和几乎 16GB 的虚拟内存。据我了解,我的所有数组和矩阵的总和不应超过 100MB - 大约 5.5Minteger(4)和 2.5M real(8)(根据我的说法应该只有大约 44MB,但公平地说,再增加 50MB 的开销)。

我使用与 Microsoft Visual Studio 2008 集成的 Intel Fortran 编译器。


添加一些实际的源代码以澄清一点

! Update continuum state
CALL UpdateContinuumState(iTask,iArray,posc,dof,dof_k,nodedof,elm,&
                    bmtrx,detjac,w,mtrlprops,demtrx,dt,stress,strain,effstrain,&
                    effstress,aa,fi,errmsg)

是对例程的实际调用。大数组是posc,bmtrx并且aa- 所有其他数组至少小一个数量级(如果不是更多)。poscINTEGER(4)和是bmtrx_aaREAL(8)

SUBROUTINE UpdateContinuumState(iTask,iArray,posc,dof,dof_k,nodedof,elm,bmtrx,&
                    detjac,w,mtrlprops,demtrx,dt,stress,strain,effstrain,&
                    effstress,aa,fi,errmsg)

    IMPLICIT NONE

    !I/O
    INTEGER(4) :: iTask, errmsg
    INTEGER(4) :: iArray(64)
    INTEGER(4),DIMENSION(iArray(15),iArray(15),iArray(5)) :: posc
    INTEGER(4),DIMENSION(iArray(22),iArray(21)+1) :: nodedof
    INTEGER(4),DIMENSION(iArray(29),iArray(3)+2) :: elm
    REAL(8),DIMENSION(iArray(14)) :: dof, dof_k
    REAL(8),DIMENSION(iArray(12)*iArray(17),iArray(15)*iArray(5)) :: bmtrx
    REAL(8),DIMENSION(iArray(5)*iArray(17)) :: detjac
    REAL(8),DIMENSION(iArray(17)) :: w
    REAL(8),DIMENSION(iArray(23),iArray(19)) :: mtrlprops
    REAL(8),DIMENSION(iArray(8),iArray(8),iArray(23)) :: demtrx
    REAL(8) :: dt
    REAL(8),DIMENSION(2,iArray(12)*iArray(17)*iArray(5)) :: stress
    REAL(8),DIMENSION(iArray(12)*iArray(17)*iArray(5)) :: strain
    REAL(8),DIMENSION(2,iArray(17)*iArray(5)) :: effstrain, effstress
    REAL(8),DIMENSION(iArray(25)) :: aa
    REAL(8),DIMENSION(iArray(14)) :: fi 

    !Locals
    INTEGER(4) :: i, e, mtrl, i1, i2, j1, j2, k1, k2, dim, planetype, elmnodes, &
        Nec, elmpnodes, Ndisp, Nstr, Ncomp, Ngpt, Ndofelm
    INTEGER(4),DIMENSION(iArray(15)) :: doflist
    REAL(8),DIMENSION(iArray(12)*iArray(17),iArray(15)) :: belm
    REAL(8),DIMENSION(iArray(17)) :: jelm
    REAL(8),DIMENSION(iArray(12)*iArray(17)*iArray(5)) :: dstrain
    REAL(8),DIMENSION(iArray(12)*iArray(17)) :: s
    REAL(8),DIMENSION(iArray(17)) :: ep, es, dep
    REAL(8),DIMENSION(iArray(15),iArray(15)) :: kelm
    REAL(8),DIMENSION(iArray(15)) :: felm

    dim       = iArray(1)
...

它在上面的最后一行之前失败了。

4

6 回答 6

9

根据 steabert 的要求,我将在此处的评论中总结对话,让对话更加明显,即使 MSB 的回答已经切入问题的核心。

在技​​术编程中,过程通常具有用于中间计算的大型本地数组,这种情况经常发生。局部变量通常存储在堆栈中,通常(并且相当合理地)占整个系统内存的一小部分——通常为 10MB 左右。当局部变量大小超过堆栈大小时,您会看到此处描述的确切症状——在调用相关子例程之后但在其第一个可执行语句之前发生堆栈溢出。

所以当这个问题发生时,最好的办法就是找到相关的大局部变量,然后决定怎么做。在这种情况下,至少变量 belm 和 dstrain 变得相当大。

一旦找到了变量,并且您已经确认这是问题所在,就有了一些选择。正如 MSB 指出的那样,如果您可以使阵列更小,那是一种选择。或者,您可以使堆栈大小更大;在 linux 下,使用ulimit -s [newsize]. 但是,这实际上只是推迟了问题,并且您必须在 Windows 机器上做一些不同的事情。

避免这个问题的另一类方法不是将大数据放在堆栈上,而是放在内存的其余部分(“堆”)中。您可以通过为数组赋予save属性来做到这一点(在 C 中,static); 这会将变量放在堆上,从而使值在调用之间保持不变。不利的一面是,这可能会改变子例程的行为,并且意味着子例程不能递归使用,并且同样是非线程安全的(如果您曾经处于多个线程同时进入例程的位置,它们'将各自看到本地变量的相同副本并可能覆盖彼此的结果)。好处是它很容易而且非常便携——它应该可以在任何地方工作。但是,这只适用于固定大小的局部变量;如果临时数组的大小取决于输入,则不能这样做(因为不再需要保存单个变量;每次调用过程时它的大小都可能不同)。

有一些特定于编译器的选项可以将所有数组(或所有大于某个给定大小的数组)放在堆上而不是堆栈上;我知道的每个 Fortran 编译器都有一个选项。对于在 OPs 帖子中使用的 ifort,它-heap-arrays在 linux 或/heap-arraysWindows 中。对于 gfortran,这实际上可能是默认设置。这有助于确保您知道发生了什么,但这意味着您必须为每个编译器设置不同的咒语以确保您的代码正常工作。

最后,您可以使有问题的数组可分配。分配的内存在堆上;但是指向它们的变量在堆栈上,因此您可以获得这两种方法的好处。此外,这是完全标准的 fortran,因此完全可移植。缺点是它需要更改代码。此外,分配过程可能会花费大量时间。因此,如果您要多次调用例程,您可能会注意到这会稍微减慢速度。(不过,这种可能的性能回归很容易修复;如果您将使用相同大小的数组调用它无数次,您可以有一个可选参数来传入预分配的本地数组并使用它,这样你只分配/释放一次)。

每次分配/解除分配看起来像:

SUBROUTINE UpdateContinuumState(iTask,iArray,posc,dof,dof_k,nodedof,elm,bmtrx,&
                    detjac,w,mtrlprops,demtrx,dt,stress,strain,effstrain,&
                    effstress,aa,fi,errmsg)

    IMPLICIT NONE

    !...arguments.... 


    !Locals
    !...
    REAL(8),DIMENSION(:,:), allocatable :: belm
    REAL(8),DIMENSION(:), allocatable :: dstrain

    allocate(belm(iArray(12)*iArray(17),iArray(15))  
    allocate(dstrain(iArray(12)*iArray(17)*iArray(5))

    !... work

    deallocate(belm)
    deallocate(dstrain)

请注意,如果子例程做了很多工作(例如,需要几秒钟的时间来执行),那么几次分配/解除分配的开销应该可以忽略不计。如果不是,并且您想避免开销,则使用预分配工作空间的可选参数看起来像:

SUBROUTINE UpdateContinuumState(iTask,iArray,posc,dof,dof_k,nodedof,elm,bmtrx,&
                    detjac,w,mtrlprops,demtrx,dt,stress,strain,effstrain,&
                    effstress,aa,fi,errmsg,workbelm,workdstrain)

    IMPLICIT NONE

    !...arguments.... 
    real(8),dimension(:,:), optional, target :: workbelm
    real(8),dimension(:), optional, target :: workdstrain
    !Locals
    !...

    REAL(8),DIMENSION(:,:), pointer :: belm
    REAL(8),DIMENSION(:), pointer :: dstrain

    if (present(workbelm)) then
       belm => workbelm
    else
       allocate(belm(iArray(12)*iArray(17),iArray(15))
    endif
    if (present(workdstrain)) then
       dstrain => workdstrain
    else
       allocate(dstrain(iArray(12)*iArray(17)*iArray(5))
    endif

    !... work

    if (.not.(present(workbelm))) deallocate(belm)
    if (.not.(present(workdstrain))) deallocate(dstrain)
于 2011-04-28T12:48:35.513 回答
4

程序启动时并非所有内存都被创建。当您调用子例程时,可执行文件正在为子例程创建局部变量所需的内存。通常,具有该子例程本地的简单声明的数组(既不是可分配的,也不是指针的)在堆栈上分配。当您到达这些声明时,您可以简单地运行堆栈空间。在带有某些阵列的 32 位操作系统上,您可能已经达到了 2GB 的限制。有时可执行语句会在堆栈上隐式创建一个临时数组。

可能的解决方案:1)使您的数组更小(不吸引人),2)使堆栈更大),3)一些编译器可以选择从将数组放在堆栈上切换到动态分配它们,类似于用于“分配”的方法, 4) 识别大数组并使它们可分配。

于 2011-04-27T15:29:47.770 回答
2

栈是函数需要返回的信息的内存区域,存储了函数本地定义的信息。因此,堆栈溢出可能表明您有一个函数调用另一个函数,而该函数又调用另一个函数,等等。

我不熟悉 Fortran(不再),但另一个原因可能是这些函数声明了大量的局部变量,或者至少是需要很多位置的变量。

最后一个:堆栈通常相当小,因此机器有多少内存与先验无关。指示链接器增加堆栈大小应该非常简单,至少如果您确定这只是空间不足而不是应用程序中的错误。

编辑:你在你的程序中使用递归吗?递归调用可以很快地吃掉堆栈。

编辑:看看这个:(强调我的)

在 Windows 上,为程序保留的堆栈空间是使用 /Fn 编译器选项设置的,其中 n 是字节数。此外,可以通过 Visual Studio IDE 指定堆栈保留大小,它将 Microsoft 链接器选项 /STACK: 添加到链接器命令行。要设置它,请转到属性页>配置属性>链接器>系统>堆栈保留大小。您可以在此处以十进制或 C 语言表示法指定堆栈大小(以字节为单位)。如果未指定, 则默认堆栈大小为 1MB

于 2011-04-26T20:21:50.543 回答
2

我在使用类似测试代码时遇到的唯一问题是 32 位编译的 2Gb 分配限制。当我超过它时,我在 winsig.c 的第 419 行收到一条错误消息

2GB 分配限制错误

这是测试代码

program FortranCon

implicit none

! Variables
INTEGER :: IA(64), S1
REAL(8), DIMENSION(:,:), ALLOCATABLE :: AA, BB
REAL(4) :: S2
INTEGER, PARAMETER :: N = 10960
IA(1)=N
IA(2)=N

ALLOCATE( AA(N,N), BB(N,N) )
AA(1:N,1:N) = 1D0
BB(1:N,1:N) = 2D0

CALL TEST(AA,BB,IA)

S1 = SIZEOF(AA)                 !Size of each array
S2 = 2*DBLE(S1)/1024/1024       !Total size for 2 arrays in Mb

WRITE (*,100) S2, ' Mb'         ! When allocation reached 2Gb then
100 FORMAT (F8.1,A)                 ! exception occurs in Win32

DEALLOCATE( AA, BB )

end program FortranCon


SUBROUTINE TEST(AA,BB,IA)
IMPLICIT NONE
INTEGER, DIMENSION(64),INTENT(IN) :: IA    
REAL(8), DIMENSION(IA(1),IA(2)),INTENT(INOUT) :: AA,BB

... !Do stuff with AA,BB        
END SUBROUTINE

N=10960它运行正常时显示1832.9 Mb。随之N=11960崩溃。当然,当我使用 x64 编译时,它可以正常工作。每个数组有 8*N^2 字节的存储空间。我不知道它是否有帮助,但我建议使用INTENT()虚拟变量的关键字。

于 2011-04-27T01:17:15.120 回答
2

你在使用一些并行化吗?这可能是静态声明的数组的问题。尝试将所有更大的数组都设为 ALLOCATABLE,否则,它们将被放在自动并行或 OpenMP 线程中的堆栈上。

于 2011-04-27T16:44:44.320 回答
0

对我来说,问题是堆栈保留大小。我去将堆栈保留大小从更改0100000000并重新编译了代码。代码现在运行顺利。

在此处输入图像描述

于 2017-04-04T18:26:58.273 回答