10
votes

Overflow de pile en fortran 90

J'ai écrit un programme assez volumineux à Fortran 90. Cela fonctionne joliment depuis un certain temps, mais aujourd'hui, j'ai essayé de monter dans une encoche et d'augmenter la taille du problème (c'est une recherche non standard FE-SOLVUVER , si cela aide tout le monde ...) Maintenant, je reçois le message d'erreur "Overflow de pile" et que le programme se termine naturellement sans me donner quoi que ce soit utile de travailler.

Le programme commence par la configuration de toutes les matrices et matrices pertinentes, et après Cela est fait, il imprime quelques lignes de statistiques à ce sujet à un fichier journal. Même avec mon nouveau problème plus vaste, cela fonctionne bien (bien que peu lent), mais il échoue alors que le "nombre craquant" devient " p>

Qu'est-ce qui me confond est que tout ce qui me confond est déjà alloué (et cela a fonctionné sans erreurs). Je ne suis pas tout à fait sûr de ce que la pile est (Wikipedia et plusieurs marches ici ne faisaient pas grand chose depuis que je n'ai que des connaissances assez fondamentales sur les fonctionnaires "derrière les scènes" d'un ordinateur). P>

suppose que je dispose par exemple des tableaux initialisés comme suit: p> xxx pré>

lequel après quelques routines d'initialisation (c'est-à-dire que la lecture de fichier à partir du fichier et de telles) est allouée (je stocke des entiers de taille pour Il est plus facile de passer aux sous-programmes d'IA de taille fixe): p> xxx pré>

Ceci est essentiellement ce qui se passe dans la partie initiale, et jusqu'à présent si bon. Mais quand j'appelle ensuite un sous-programme p> xxx pré>

et la routine ressemble à (rien de fantaisie): p> xxx pré>

maintenant je reçois une erreur! La sortie à l'écran dit: p> xxx pré>

Cependant, lorsque j'exécute le programme avec le débogueur, il se casse à la ligne 419 dans un fichier appelé winsig.c code > (pas mon fichier, mais fait probablement partie du compilateur?). Il semble faire partie d'une routine appelée sigreterror: code> et c'est le cas par défaut qui a été appelé, renvoyer le texte signal ou erreur non valide code>. Il y a une ligne de commentaire attachée à celle-ci qui dit étrangement / * ne devrait jamais arriver, mais le compilateur ne peut pas dire * / code> ...? P>

Donc, je suppose que ma question est, Pourquoi cela se produit-il et ce qui se passe réellement? Je pensais que tant que je peux allouer tout la mémoire pertinente, je devrais être bien? L'appel au sous-programme fait-il une copie des arguments ou simplement des pointeurs? Si la réponse est des copies, je peux voir où le problème pourrait être, et si oui: des idées sur la façon de se déplacer? P>

Le problème que j'essaie de résoudre est grand, mais pas fou de quelque manière que ce soit . Standard Fe-Solvers peut gérer des problèmes plus importants que mon actuel. Je gère le programme sur un Dell PowerEdge 1850 et le système d'exploitation est Microsoft Server 2008 R2 Enterprise. Selon Systeminfo code> à l'invite code> CMD CODE> J'ai 8 Go de mémoire physique et presque 16 Go virtuelle. Pour autant que je comprenne le total de tous mes matrices et matrices ne doit pas ajouter à plus de 100 Mo - environ 5,5 m entier (4) code> et 2.5m réel (8) code > (qui, selon moi, ne devrait être qu'environ 44 Mo, mais soyons juste et ajoutons encore 50 Mo pour les frais généraux). P>

J'utilise le compilateur Intel Fortran intégré à Microsoft Visual Studio 2008. P>


Ajout d'un code source réel pour clarifier un bit P>

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)
...


14 commentaires

Avez-vous de grandes variables locales - des tableaux temporaires de taille significative, disent - défini pour une utilisation dans la routine_one?


Il y a quelques sous-routines appelées dans cette routine, oui. Mais ils ne sont appelés qu'un à la fois, et chacun des habitants est très petit par rapport aux grandes tableaux dans la principale.


De plus, je fixe un point d'arrêt à la première ligne exécutable dans la routine une et elle échoue avant de l'atteindre.


Avez-vous de grandes routines définies quelque part qui ne sont pas définies comme allouées - c'est-à-dire une taille fixe? Quelque part une grande quantité de données définies sur la pile, dans ce contexte, généralement des variables locales et que vous rencontrez des problèmes. Vous devrez peut-être fournir plus de code ou exécuter le code via un débogueur et découvrir où exactement l'erreur se produit. Notez que la taille de la pile est beaucoup plus petite que la quantité de mémoire totale disponible, et donc quelques MB peuvent suffire.


Donc, il échoue après l'appel à routine_one, mais avant la première déclaration exécutable de la routine? D'accord; Pouvez-vous publier l'ensemble complet de déclarations variables locales, etc., pour cette routine?


Ok, alors cela me semble que comme Belm et Dstrain y se lève avec BMTRX de taille. En tant que test simple, pouvez-vous donner à ceux-ci le enregistrez (par exemple, réel (8), enregistrement, dimension ... ) et voyez si vous obtenez plus loin? Si cela ne fonctionne pas, avec ifort, vous pouvez être plus drastique et mettre tout sur le tas; Dans Linux, il est avec le drapeau -heap-tableaux , sous Windows, je suppose que ce serait quelque chose comme / tas de tas . Si cela fonctionne, au moins, vous savez ce qu'est le problème sous-jacent. Les grandes matrices locales pourraient être traitées en leur faisant sauver ou en les allouant explicitement.


@Jonathan Dursi poing et avant tout; Merci pour votre aide! J'ai essayé l'attribut de sauvegarde mais maintenant mon compilateur me dérange. Il affirme qu'un objet automatique (?) Ne doit pas apparaître dans une inscription ou être déclarée avec l'attribut Enregistrer. Pour être honnête, je n'ai aucune idée de ce que cela signifie. Jamais utilisé d'économiser avant. Vous avez raison à propos de Dstrain étant assez grand. Vous pensez que l'erreur se produit lorsque j'essaie d'allouer les habitants du sous-programme?


Oui, et je vois ce que cela signifie sur l'attribut de sauvegarde. Le problème est que les matrices que j'ai signalées sont définies sont dimensionnées au moment de l'exécution, en fonction des valeurs de iarray. Donc, cela ne peut pas sauver celles - j'aurais dû voir que mes appologies. L'alternative serait de les rendre allouées et de les affecter - mais d'abord, pouvez-vous essayer l'option / des tableaux de tas au compilateur et voir si cela fait disparaître le problème? Si tel est le cas, nous pouvons nous concentrer sur la gestion des tableaux particuliers qui causent des problèmes.


Ok, il devient tard ici en Suède et ma tête tourne de toutes ces nouvelles idées (drôle, je pensais en fait avoir une très bonne compréhension des bases de la Fortrane). Ira un autre tour avec ce demain. BTW, comment puis-je indiquer que les commentaires d'un utilisateur particulier ont été utiles?


@Jonathan Le réparateur des tableaux de tas a travaillé! Maintenant, je l'ai défini pour que tous les tableaux sont alloués / stockés (?) Au tas. Je suppose que je pouvais trouver une limite accrue sur la taille au-dessus duquel un tableau doit être mis sur le tas au lieu de la pile? CPU-Time est-il un problème avec cela? Le pré-traitement a réellement pris 12 secondes de moins maintenant (501 au lieu de 513) - Fluke?


Cela pourrait gagner du temps si vous appelez un lot de routines; Je n'attendrais normalement pas un changement important d'une manière ou d'une autre. Si cela fonctionne, je dirais que les grandes matrices locales sont définitivement le problème. Ma suggestion serait d'aller après quelques-uns des plus grandes matrices locales et de les rendre allouées et de les affecter; Cela les mettra aussi sur le tas, mais d'une manière qui ne dépend pas des drapeaux spécifiques au compilateur.


@Jonathan Dursi: Pourriez-vous en quelque sorte mettre la solution de réseau de démarrage dans une réponse ci-dessous? La solution est un peu cachée dans tous les commentaires maintenant.


@Jonathan Dursi J'ai fait les grandes matrices locales allouées et utilisées l'option Sauvegarder, mais cela n'a pas fonctionné. Je reçois la même erreur au même endroit dans le programme. J'ai également essayé de ne mettre que les tableaux de 100 kb ou plus grand sur le tas au lieu de la pile - fonctionne juste bien. J'ai des problèmes avec le programme qui se comporte erratique et ne converge pas lorsque vous essayez de résoudre le problème plus vaste, qui est gênant, mais je suppose sans rapport avec la question du dépassement de la pile.


EDIT: Le commentaire ci-dessus n'était pas entièrement vrai que je réalise. Il a échoué avec l'allouat et la sauvegarde, mais pas exactement à la même ligne qu'auparavant. Il a échoué à l'allocation (tous les tableaux plus grands) un peu plus bas. Désolé pour ça - mon mauvais. Une fois que j'ai enlevé la sauvegarde, ça marche. Devinez qu'il a essayé d'allouer des tableaux sauvegardés, mais ils étaient déjà déjà là sur la deuxième passe dans le sous-programme ...


6 Réponses :


2
votes

La pile est la zone de mémoire où les informations nécessaires à la fonction d'une fonction et que les informations définies localement dans une fonction sont stockées. Donc, un débordement de pile peut indiquer que vous avez une fonction qui appelle une autre fonction qui appelle à son tour une autre fonction, etc.

Je ne suis pas familier avec Fortran (plus) mais une autre cause peut être que ces fonctions déclarent des tonnes de variables locales ou au moins des variables nécessitant beaucoup d'endroit.

Un dernier: la pile est généralement assez petite, donc ce n'est pas a priori pertinent combien de mémoire la machine a. Il devrait être assez simple d'indiquer à la liaison d'augmenter la taille de la pile, au moins si vous êtes certain que c'est un manque d'espace et non un bogue dans votre application.

Edit: utilisez-vous la récursive dans votre programme? Les appels récursifs peuvent manger très rapidement la pile.

Edit: Regardez à ce : (met l'accent sur la mine)

sur Windows, l'espace de pile à réservé au programme est défini en utilisant l'option / fn compiler, où n est le nombre d'octets. En outre, La taille de la réserve de pile peut être spécifié via le studio visuel IDE qui ajoute la liaison Microsoft Option / Stack: à la commande Linker ligne. Pour mettre cela, allez à la propriété Pages> Configuration Propriétés> Linker> Système> Réserve de pile Taille. Là vous pouvez spécifier la pile taille en octets en décimal ou Notation de langue C. Si non spécifié, La taille de la pile par défaut est de 1 Mo .


4 commentaires

Merci pour la réponse rapide. Nope, pas de récursivité. Je vais voir ce que je peux trouver sur l'augmentation de la taille de la pile.


@Carl utilisez-vous GNU FORTRAN et LD?


Non, le compilateur Intel Fortran et Microsoft Visual Studio.


Oui, j'ai vu cela plus tôt aujourd'hui. Ce qui est étrange, c'est que je peux, sans aucun problème, ce que jamais, exécutez des problèmes d'environ la moitié de la taille du problème actuel.



2
votes

Le seul problème que j'ai rencontré avec un code de test similaire est la limite d'allocation de 2 Go pour la compilation 32 bits. Lorsque je dépasse, je reçois un message d'erreur sur la ligne 419 dans winsig.c

erreur de limite d'allocation 2 Go < / p>

voici le code de test xxx

lorsque n = 10960 Il exécute OK montrant 1832.9 MB . Avec n = 11960 il se bloque. Bien sûr, lorsque je compile avec X64, cela fonctionne bien. Chaque tableau a 8 * N ^ 2 octets de stockage. Je ne sais pas si cela aide mais je recommande d'utiliser les mots-clés () pour les variables factices.


2 commentaires

Ok, mais c'est un problème avec la limite de la taille d'un seul tableau que je suppose? Mon plus grand tableau est d'environ 100 fois plus petit que cela. Puis-je utiliser l'intention Ketwords sans écrire des sections d'interface explicites? Si oui, je conviens que cela semble être une bonne pratique. J'ai utilisé cela pendant un moment, mais il a eu un peu volumineux avec toute l'interface que je devais garder à jour une fois que j'ai changé quelque chose.


Vous ne devriez pas avoir besoin d'écrire des interfaces, à l'exception des cas particuliers, tels que l'appelant d'autres langues ... Il est préférable de mettre vos procédures dans des modules, puis de «utiliser» le module du programme principal ou d'autres procédures qui les utilisent. Cela va attraper des incohérences d'interface, un bug commun. Il est facile et raccourcira le cycle de développement.



4
votes

Tous les mots ne sont pas créés lorsque le programme commence. Lorsque vous appelez le sous-programme, l'exécutable crée la mémoire dont le sous-programme a besoin de variables locales. Généralement des tableaux avec des déclarations simples locales à ce sous-programme - ni allouatable, ni pointeur - ne sont attribués sur la pile. Vous auriez pu simplement gérer de l'espace de pile lorsque vous avez atteint ces déclarations. Vous avez peut-être atteint une limite de 2 Go sur un système d'exploitation 32 bits avec un tableau. Parfois, des déclarations exécutables créent implicitement un tableau temporaire sur la pile.

Solutions possibles: 1) Faites de vos matrices plus petites (non attrayantes), 2) Faire la pile plus grande), 3) Certains compilateurs ont des options pour passer de tableaux de placement sur la pile pour les affecter de manière dynamique, similaire à la méthode utilisée pour "Allouer", 4) Identifiez les grandes matrices et les rendre allocalisables.


0 commentaires

2
votes

Utilisez-vous une certaine parallélisation? Cela peut être un problème avec des tableaux déclarés statiquement. Essayez tous les raies plus gros rendent allouatable, sinon, ils seront placés sur la pile en fils autoparallel ou openmp.


0 commentaires

9
votes

Selon la demande de SteaSbert, je vais simplement résumer la conversation dans les commentaires ici où il est un peu plus visible, même si la réponse de M.S.B. est déjà dûment droit à la nuit du problème.

Dans la programmation technique, où les procédures ont souvent de grandes matrices locales pour le calcul intermédiaire, cela se produit beaucoup. Les variables locales sont généralement stockées sur la pile, qui typiquement (et assez raisonnablement) une petite fraction de la mémoire globale du système - généralement d'ordre 10MB environ. Lorsque les tailles de variables locales dépassent la taille de la pile, vous voyez exactement les symptômes décrits ici - un débordement de pile survenu après un appel au sous-programme concerné, mais avant sa première déclaration exécutable.

Ainsi, alors que ce problème se produit, La meilleure chose à faire est de trouver les grandes variables locales pertinentes et de décider quoi faire. Dans ce cas, au moins les variables BELM et DStrain deviennent assez importantes.

Une fois que les variables sont localisées et que vous avez confirmé que c'est le problème, il y a quelques options. Comme MSB souligne, si vous pouvez rendre vos matrices plus petites, c'est une option. Alternativement, vous pouvez faire la taille de la pile plus grande; Sous Linux, cela est fait avec ulimit -s [Newsize] . Cela ne fait que reporter simplement le problème, cependant, et vous devez faire quelque chose de différent sur les machines Windows.

L'autre classe de manières d'éviter ce problème n'est pas de mettre les grandes données sur la pile, mais dans le reste de mémoire (le "tas"). Vous pouvez le faire en donnant aux tableaux l'attribut enregistrez (en C, statique ); Cela met la variable sur le tas et rend ainsi les valeurs persistantes entre les appels. L'inconvénient est que cela modifie potentiellement le comportement du sous-programme et signifie que le sous-programme ne peut pas être utilisé de manière récursive et non de non-threadsafe (si vous êtes dans une position dans laquelle plusieurs threads entreront dans la routine simulatne, elles On voit chacun la même copie de la varaitable locale et potentiellement écrasé les résultats de chacun). L'avantage est que c'est facile et très portable - il devrait fonctionner partout. Cependant, cela ne travaillera que avec des variables locales de taille fixe; Si les matrices temporaires ont des tailles qui dépendent des entrées, vous ne pouvez pas le faire (car il n'y avait plus une seule variable à enregistrer; il pourrait être une taille différente chaque fois que la procédure est appelée). < p> Il existe des options spécifiques au compilateur qui mettent toutes des tableaux (ou tous les tableaux de taille supérieure à une taille donnée) sur le tas plutôt que sur la pile; Chaque compilateur de Fortran que je connais a une option pour cela. Pour IFORT, utilisé dans la POST OPS, c'est -heap-tableaux sous Linux ou / tas de tas pour Windows. Pour gfortran, cela peut en réalité être la valeur par défaut. C'est bon pour vous assurer de savoir ce qui se passe, mais cela signifie que vous devez avoir des incantations différentes pour chaque compilateur pour vous assurer que votre code fonctionne.

Enfin, vous pouvez faire des matrices incriminées allouées. La mémoire allouée va sur le tas; Mais la variable qui leur pointe est sur la pile, vous bénéficiez donc des avantages des deux approches. En outre, cela est complètement standard pourtran et donc totalement portable. L'inconvénient est que cela nécessite des changements de code. En outre, le processus d'attribution peut prendre des quantités non triviales; Donc, si vous allez appeler les zillions de routine de fois, vous remarquerez peut-être que cela ralentit légèrement les choses. (Cette éventuelle régression de la performance est facile à corriger, cependant; si vous l'appelez des zillions de fois avec les matrices de même taille, vous pouvez avoir un argument facultatif pour passer dans un tableau local pré-alloué et utiliser cela, de sorte que Vous n'allouez que tous seulement).

alloué / trafiquant chaque fois ressemblerait à: xxx

Notez que si le sous-programme fait beaucoup de travail (par exemple, prend des secondes à exécuter), Les frais généraux d'un couple allouent / trafiquant devraient être négligables. Sinon, et vous voulez éviter les frais généraux, l'utilisation des arguments facultatifs de PrealloCated Womanpace ressemblerait à quelque chose comme: xxx


2 commentaires

Je sais que ce n'est que anecdotique, mais à partir de mes tests, la méthode avec allouer / distribuer est beaucoup plus rapide que de tout mettre (ou simplement des tableaux plus importants) sur le tas. Presque un facteur 2 dans la différence de processeur. Encore une fois @jonathan Dursi merci pour toute l'aide!


Code corrige après la suggestion de rejeté (à tort, de l'IMHO) Modifier proposé par @Alberto



0
votes

Pour moi, le problème était la taille de la réserve de pile. Je suis allé et j'ai changé la taille de la pile réservée de 0 à 100000000 et recompanté le code. Le code fonctionne maintenant bien.

Entrez la description de l'image ici


0 commentaires