我已经在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:
的例程的一部分,它是已调用的默认情况,返回文本Invalid signal or error
。附加了一条评论专栏,奇怪地说/* should never happen, but compiler can't tell */
......?
所以我想我的问题是,为什么会发生这种情况,实际发生了什么?我认为只要我可以分配所有相关的内存我应该没问题?对子程序的调用是否会复制参数,或只是指向它们的指针?如果答案是副本,那么我可以看到问题可能在哪里,如果是的话:关于如何绕过它的任何想法?
我试图解决的问题很大,但不以任何方式疯狂。标准FE解算器可以处理比我现在更大的问题。我在Dell PowerEdge 1850上运行程序,操作系统是Microsoft Server 2008 R2 Enterprise。根据{{1}} systeminfo
提示,我有8GB的物理内存和近16GB的虚拟内存。据我所知,我的所有数组和矩阵的总和不应超过100MB - 约5.5M cmd
和2.5M integer(4)
(根据我的说法,应该只有44MB左右,但是,让我们公平,再增加50MB的开销。
我使用与Microsoft Visual Studio 2008集成的英特尔Fortran编译器。
添加一些实际的源代码以澄清一点
real(8)
是对例程的实际调用。大数组是! 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
为posc
,INTEGER(4)
和bmtrx
为aa
REAL(8)
它在上一行之前失败了。
答案 0 :(得分:7)
根据steabert的要求,我将在这里的评论中总结一下这里的对话,它更加明显,尽管M.S.B.的答案已经正确地解决了问题的核心。
在技术编程中,程序通常具有用于中间计算的大型本地数组,这发生了很多。局部变量通常存储在堆栈中,这通常(并且相当合理地)是整个系统内存的一小部分 - 通常为10MB左右。当局部变量大小超过堆栈大小时,您可以看到此处描述的症状 - 在调用相关子例程之后但在其第一个可执行语句之前发生堆栈溢出。
因此,当出现此问题时,最好的办法是找到相关的大型局部变量,并决定要做什么。在这种情况下,至少变量belm和dstrain变得非常大。
一旦找到变量,并且您已确认问题所在,就有几个选项。正如MSB所指出的,如果你可以让你的阵列更小,那就是一个选择。或者,您可以使堆栈大小更大;在linux下,这是用ulimit -s [newsize]
完成的。但这确实只是推迟了问题,你必须在Windows机器上做一些不同的事情。
避免此问题的另一类方法不是将大数据放在堆栈上,而是放在内存的其余部分(“堆”)中。你可以通过给数组赋予save
属性(在C中,static
)来做到这一点;这会将变量放在堆上,从而使值在调用之间保持不变。缺点是这可能会改变子程序的行为,并且意味着子程序不能递归使用,同样也是非线程安全的(如果你曾经处于多线程将同时进入例程的位置,它们每个人都会看到本地变量的相同副本,并可能会覆盖彼此的结果。好处是它简单易用 - 它应该可以在任何地方使用。但是,这只适用于固定大小的局部变量;如果临时数组的大小取决于输入,则不能这样做(因为不再需要保存单个变量;每次调用过程时它的大小可能不同。)
有特定于编译器的选项可以将所有数组(或所有大于某个给定大小的数组)放在堆上而不是堆栈上;我知道的每个Fortran编译器都有一个选项。对于在OPs帖子中使用的ifort,它在linux中为-heap-arrays
,对于windows为/heap-arrays
。对于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)
请注意,如果子例程执行了大量工作(例如,执行需要几秒钟),则一对分配/解除分配的开销应该是可以忽略的。如果没有,并且你想避免开销,使用preallocated worskpace的可选参数看起来像:
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)
答案 1 :(得分:2)
程序启动时并非所有内存都已创建。当您调用子例程时,可执行文件正在创建子例程对局部变量所需的内存。通常,具有该子例程本地的简单声明的数组 - 既不可分配也不指针 - 都在堆栈上分配。当你到达这些声明时,你可以简单地运行堆栈空间。对于具有某些阵列的32位操作系统,您可能已达到2GB的限制。有时,可执行语句会在堆栈上隐式创建临时数组。
可能的解决方案:1)使您的阵列更小(不吸引人),2)使堆栈更大),3)一些编译器可以选择从堆栈上放置数组切换到动态分配它们,类似于用于“allocate”,4)识别大型数组并使它们可分配。
答案 2 :(得分:1)
堆栈是存储区域,其中存储从函数返回所需的信息,并存储函数中本地定义的信息。因此,堆栈溢出可能表示您有一个函数调用另一个函数,该函数又调用另一个函数等。
我不熟悉Fortran(不再)了,但另一个原因可能是这些函数声明了大量的局部变量,或者至少需要很多地方的变量。
最后一个:堆栈通常很小,所以它不是先验相关的机器有多少内存。指示链接器增加堆栈大小应该非常简单,至少如果您确定它只是缺少空间,而不是应用程序中的错误。
编辑:你在程序中使用递归吗?递归调用可以非常快速地通过堆栈进行。
编辑:看看at this :(强调我的)
在Windows上,堆栈空间为 为程序保留使用 / Fn编译器选项,其中n是 字节数。另外, 堆栈保留大小可以 通过Visual Studio指定 添加Microsoft链接器的IDE 选项/ STACK:到链接器命令 线。要设置此项,请转到“属性” 页面>配置 属性>链接器>系统>堆栈保留 尺寸。在那里你可以指定堆栈 以十进制或小数字节为单位的大小 C语言符号。如果没有指定, 默认堆栈大小为1MB 。
答案 3 :(得分:1)
我遇到类似测试代码的唯一问题是32位编译的2Gb分配限制。当我超过它时,我在winsig.c的第419行收到错误消息
这是测试代码
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()
关键字作为虚拟变量。
答案 4 :(得分:1)
您使用的是并行化吗?这可能是静态声明的数组的问题。尝试使用所有更大的数组进行ALLOCATABLE,否则,它们将被置于自动并行或OpenMP线程的堆栈中。
答案 5 :(得分:0)
对我来说,问题是堆栈保留大小。我去了并将堆栈保留大小从0
更改为100000000
并重新编译了代码。代码现在运行顺利。