Переполнение стека в Fortran 90

я написал довольно большую программу в 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

однако, когда я запускаю программу с отладчиком, она ломается в строке 419 в файле с именем winsig.c (Не мой файл, но, вероятно, часть компилятора?). Кажется, это часть рутины под названием sigreterror: и это дело по умолчанию, который был вызван, возвращая текст Invalid signal or error. К этому прилагается строка комментария, которая странно говорит /* should never happen, but compiler can't tell */ ...?

так что я думаю, мой вопрос, почему это происходит и что на самом деле происходит? Я думал, что пока я могу выделить всю соответствующую память, я должен быть в порядке? Делает ли вызов подпрограммы копии аргументов или просто указывает на них? Если ответ-копии, тогда я вижу, где может быть проблема, и если да, то какие-нибудь идеи о том, как ее обойти?

проблема, которую я пытаюсь решить, большая, но никак не безумная. Стандартные Fe-решатели могут справляюсь с большими проблемами, чем моя нынешняя. Я запускаю программу на Dell PowerEdge 1850, а ОС-Microsoft Server 2008 R2 Enterprise. Согласно systeminfo на cmd подсказка у меня есть 8 ГБ физической памяти и почти 16 ГБ виртуальной. Насколько я понимаю, общая сумма всех моих массивов и матриц не должна составлять более 100 МБ - около 5,5 м integer(4) и 2,5 м real(8) (что, по моему мнению, должно быть только около 44 Мб, но давайте будем справедливы и добавим еще 50 МБ для надземный.)

я использую компилятор Intel Fortran, интегрированный с Microsoft Visual Studio 2008.


добавление фактического исходного кода для уточнения немного

! 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 is INTEGER(4) и bmtrx и aa и REAL(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)
...

и он терпит неудачу перед последней строкой выше.

6 ответов


в соответствии с просьбой стиберта, я просто подытожу разговор в комментариях здесь, где он немного более заметен, хотя ответ M. S. B. уже получает право на суть проблемы.

в техническом программировании, где процедуры часто имеют большие локальные массивы для промежуточных вычислений, это происходит много. Локальные переменные обычно хранятся в стеке, который обычно (и вполне разумно) небольшая часть общей системной памяти - обычно порядка 10МБ или так. Когда размеры локальных переменных превышают размер стека, вы видите точно описанные здесь симптомы-переполнение стека происходит после вызова соответствующей подпрограммы, но до ее первого исполняемого оператора.

поэтому, когда эта проблема возникает, лучше всего найти соответствующие большие локальные переменные и решить, что делать. В этом случае, по крайней мере, переменные belm и dstrain становились довольно значительными.

как только переменные расположены, и вы подтвердили, что это проблема, есть несколько вариантов. Как указывает MSB, если вы можете сделать свои массивы меньше, это один из вариантов. Кроме того, вы можете увеличить размер стека; в linux это делается с помощью ulimit -s [newsize]. Что просто откладывает проблему, хотя, и вы должны сделать что-то другое на машинах Windows.

другой класс способов избежать этой проблемы-не помещать большие данные в стек, а в остальную память ("куча"). Вы можете сделать это, давая массивам save атрибут (в C,static); это помещает переменную в кучу и, таким образом, делает значения постоянными между вызовами. Недостатком является то, что это потенциально изменяет поведение подпрограммы и означает, что подпрограмма не может использоваться рекурсивно и аналогично не является потокобезопасной (если вы когда-либо были в положении, когда несколько потоков будут входить в подпрограмму simulatneously, они будут видеть одну и ту же копию локального varaiable и потенциально перезаписывать результаты друг друга). Плюс в том, что это легко и очень портативно-это должно работать везде. Однако это будет работать только с локальными переменными фиксированного размера; если временные массивы имеют размеры, зависящие от входных данных, вы не можете этого сделать (поскольку больше не будет одной переменной для сохранения; она может быть разного размера каждый раз, когда вызывается процедура).

существуют параметры компилятора, которые помещают все массивы (или все массивы большего размера) в кучу вместо стека; у каждого компилятора Fortran, которого я знаю, есть опция для этого. Для ifort, используемого в OPs post, это -heap-arrays в linux или /heap-arrays для windows. Для 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)

обратите внимание, что если подпрограмма выполняет много работы (например, занимает секунды для выполнения), накладные расходы от пары выделяют/освобождают должны быть незначительными. Если нет, и вы хотите избежать, используя дополнительные аргументы для предварительно 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)

Не вся память создается при запуске программы. При вызове подпрограммы исполняемый файл создает память, необходимую подпрограмме для локальных переменных. Обычно массивы с простыми объявлениями, которые являются локальными для этой подпрограммы - ни allocatable, ни pointer-выделяются в стеке. Вы могли бы просто запустить пространство стека, когда вы достигли этих объявлений. Возможно, вы достигли предела 2GB на 32-разрядной ОС с некоторым массивом. Иногда исполняемые операторы неявно создают временный массив в стеке.

возможные решения: 1) Сделайте ваши массивы меньше (не привлекательными), 2) Сделайте стек больше), 3) Некоторые компиляторы имеют опции для переключения с размещения массивов в стеке на динамическое их распределение, аналогично методу, используемому для "выделения", 4) Определите большие массивы и сделайте их распределяемыми.


стек-это область памяти, в которой хранится информация, необходимая для возврата из функции, и информация, локально определенная в функции. Так что переполнение стека может означать, у вас есть функция, которая вызывает другую функцию, которая в свою очередь вызывает другую функцию и т. д.

Я не знаком с Fortran (больше), но другой причиной может быть то, что эти функции объявляют тонны локальных переменных или, по крайней мере, переменных, которым нужно много места.

в последнюю первый: стек обычно довольно мал, поэтому априори не имеет значения, сколько памяти имеет машина. Должно быть довольно просто указать компоновщику увеличить размер стека, по крайней мере, если вы уверены, что это просто недостаток места, а не ошибка в вашем приложении.

Edit: используете ли вы рекурсию в своей программе? Рекурсивные вызовы могут очень быстро прогрызть стек.

Edit: посмотрите в этом: (выделено мной)

в Windows пространство стека для зарезервирован для программы устанавливается с помощью параметр компилятора /Fn, где n количество байт. Вдобавок, размер резерва стека может быть указывается через Visual Studio IDE, которая добавляет Компоновщик Microsoft option / STACK: к команде компоновщика линия. Для этого перейдите в собственность Страницы>Конфигурация Свойства - >Компоновщик - >Система - >Стек Резерв Размер. Там вы можете указать стек размер в байтах в десятичном или Си нотация. Если не указано, размер стека по умолчанию-1MB.


единственная проблема, с которой я столкнулся с аналогичным тестовым кодом,-это ограничение выделения 2Gb для 32-битной компиляции. Когда я превышаю его, я получаю сообщение об ошибке в строке 419 в winsig.c

2GB Allocation Limit Error

вот тестовый код

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() ключевые слова для фиктивная переменная.


вы используете некоторую распараллеливание? Это может быть проблемой со статически объявленными массивами. Попробуйте все большие массивы сделать выделяемыми, иначе они будут размещены в стеке в потоках autoparallel или OpenMP.


для меня проблема заключалась в размере резерва стека. Я пошел и изменил размер зарезервированного стека с 0 до 100000000 и перекомпилировать код. Код теперь работает гладко.

enter image description here