Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Taille de la pile

17 réponses
Avatar
roger_that
Bonjour,

Je me pose des questions par rapport à la taille de la pile qu'on peut
définir dans son compilateur (VC6 dans mon cas). Si j'ai bien compris,
la taille qu'on spécifie dans les options du projet (ou par l'options
/STACK ou par le programme Editbin) est une taille maximale utilisable
pour la pile. Cela garantit au démarrage une réservation de l'espace
d'adressage.
Au fur et à mesure de l'exécution du programme, lorsque celui-ci accède
à de nouvelles pages de la pile, le système alloue effectivement ces
pages. C'est ce que j'ai vu avec mon debugger (les "???" de la mémoire
inaccessible se transforment en mémoire accessible lorsque le programme
écrit dedans). Et d'autre part, la consommation mémoire du processus
augmente avec l'accès en écriture dans la pile.

Il est bien plus rapide d'"allouer" des buffers locaux sur la pile que
sur le tas, donc on a intéret à maximiser la taille dispo pour la pile.
Bon, pour un x86 on risque d'être limité par l'étroitesse de l'espace
d'adressage, mais sur les AMD-64, j'imagine qu'on peut voir large (par
exemple allouer 4Go d'adressage pour la pile).

Partagez-vous mon analyse ?
Merci de votre attention.

10 réponses

1 2
Avatar
Cyrille Szymanski
On 2004-04-19, roger_that <fabsk+ wrote:
Il est bien plus rapide d'"allouer" des buffers locaux sur la pile que
sur le tas, donc on a intéret à maximiser la taille dispo pour la pile.



C'est beaucoup plus rapide mais tant que la pile sera exécutable cela
posera de gros problèmes au niveau sécurité. Il faut faire attention
à ce que l'on fait. Un programme qui corrompt le tas c'est pas
trop grave (voir plus loin) mais un programme qui corrompt la pile...

Le gros problème sous Windows c'est les implémentations foireuses
de malloc() &co., du genre :
* elles placent des méta informations entre les blocs ce qui fait
qu'un programme qui écrit en dehors d'une zone allouée est
susceptible de corrompre ces données et de faire planter le
programme de façon aléatoire.
* pour allouer un bloc il faut généralement traverser tous ceux
du tas avant de trouver un espace libre, ce qui est cata-
strophique au niveau performance.
* les routines free() sont tellement idiotes qu'elles peuvent essayer
de libérer un pointeur qui n'a pas été alloué auparavent.
Je rappelle que malloc() alloue sa mémoire dans un espace déterminé
et c'est pas trop dur de vérifier au moins si ce pointeur appartient
à cette zone.
etc...

Rien qu'en changeant de routine malloc() &co. on y gagne.

Et c'est sans parler des techniques d'optimisation (buffer pool :
allouer de la mémoire par avance et réutiliser les buffers...)

Bon, pour un x86 on risque d'être limité par l'étroitesse de l'espace
d'adressage, mais sur les AMD-64, j'imagine qu'on peut voir large (par
exemple allouer 4Go d'adressage pour la pile).



Tu es limité par la taille de la mémoire virtuelle avant celle de l'es-
pace d'adressage. Je rappelle que les processus sous windows s'exécutent
tous dans un segment virtuel de 4Go. En gros si le tas peut pas faire
4Go, la pile le pourra pas plus.

--
cns
Avatar
Arnaud Debaene
Cyrille Szymanski wrote:
On 2004-04-19, roger_that <fabsk+ wrote:
Il est bien plus rapide d'"allouer" des buffers locaux sur la pile
que sur le tas, donc on a intéret à maximiser la taille dispo pour
la pile.



C'est beaucoup plus rapide mais tant que la pile sera exécutable cela
posera de gros problèmes au niveau sécurité. Il faut faire attention
à ce que l'on fait. Un programme qui corrompt le tas c'est pas
trop grave (voir plus loin) mais un programme qui corrompt la pile...

Le gros problème sous Windows c'est les implémentations foireuses
de malloc() &co., du genre :


Ce n'est pas Windows qui implémente malloc : c'est la CRT que tu utilises :
celle de VC, celle de Borland ou autre... Tu penses à laquelle en
particulier?

* elles placent des méta informations entre les blocs ce qui fait
qu'un programme qui écrit en dehors d'une zone allouée est
susceptible de corrompre ces données et de faire planter le
programme de façon aléatoire.
* pour allouer un bloc il faut généralement traverser tous ceux
du tas avant de trouver un espace libre, ce qui est cata-
strophique au niveau performance.


Tu connais des implémentations d'allocateur généraliste (pas de taille fixe,
etc...) qui font autrement sans obtenir rapidement une fragmentation
catastrophique?

* les routines free() sont tellement idiotes qu'elles peuvent essayer
de libérer un pointeur qui n'a pas été alloué auparavent.
Je rappelle que malloc() alloue sa mémoire dans un espace déterminé
et c'est pas trop dur de vérifier au moins si ce pointeur
appartient à cette zone.


Raison de performances pour le coup : appeler free sur un pointeur non aloué
par malloc est un comportement indéfini : à prtir de là, ajouter des tests
supplémentaires est certes une aide au débogage mais également un frein aux
perfs.

Rien qu'en changeant de routine malloc() &co. on y gagne.


Et en la remplacant par?


Et c'est sans parler des techniques d'optimisation (buffer pool :
allouer de la mémoire par avance et réutiliser les buffers...)


Bien sûr, c'est juste beaucoup de boulot en plus.

Bon, pour un x86 on risque d'être limité par l'étroitesse de l'espace
d'adressage, mais sur les AMD-64, j'imagine qu'on peut voir large
(par exemple allouer 4Go d'adressage pour la pile).



Tu es limité par la taille de la mémoire virtuelle avant celle de
l'es- pace d'adressage. Je rappelle que les processus sous windows
s'exécutent tous dans un segment virtuel de 4Go. En gros si le tas
peut pas faire 4Go, la pile le pourra pas plus.


Mais est ce que ce n'est pas exactement ce que vient de dire roger_that ???

Arnaud
Avatar
Arnaud Debaene
roger_that wrote:
Bonjour,

Je me pose des questions par rapport à la taille de la pile qu'on peut
définir dans son compilateur (VC6 dans mon cas). Si j'ai bien compris,
la taille qu'on spécifie dans les options du projet (ou par l'options
/STACK ou par le programme Editbin) est une taille maximale utilisable
pour la pile. Cela garantit au démarrage une réservation de l'espace
d'adressage.


Exact.

Au fur et à mesure de l'exécution du programme, lorsque celui-ci
accède à de nouvelles pages de la pile, le système alloue
effectivement ces pages. C'est ce que j'ai vu avec mon debugger (les
"???" de la mémoire inaccessible se transforment en mémoire
accessible lorsque le programme écrit dedans). Et d'autre part, la
consommation mémoire du processus augmente avec l'accès en écriture
dans la pile.

Il est bien plus rapide d'"allouer" des buffers locaux sur la pile que
sur le tas, donc on a intéret à maximiser la taille dispo pour la
pile.


Comment as-tu vu que c'était "beaucoup plus rapide"? Tu l'as mesuré?

Bon, pour un x86 on risque d'être limité par l'étroitesse de
l'espace d'adressage, mais sur les AMD-64, j'imagine qu'on peut voir
large (par exemple allouer 4Go d'adressage pour la pile).


L'espace d'adressage contient à la fois la pile et le tas, donc les
limitations de 4 GO (2GO en fait pour l'espace utilisateur) sont les mêmes
dans les 2 cas. Le seul avantage de la solution pile et qu'il n'y a pas de
fragmentation de l'espace d'adressage du processus.

Partagez-vous mon analyse ?


"Premature optimization is the root of all evil". Est-ce que tu fais cette
analyse après avoir mesuré que sur un soft précis l'allocation sur le tas
posait un problème de performances? Si ce n'est pas le cas, je pense
qu'utiliser ce genre de modifications "par principe" pour les perfs est une
mauvaise habitude qui nuit essentiellement à la maintenaiblité du code sans
forcément améliorer grand chose.
Quand à savoir le gain réel qu'apporterait ton idée, la seule solution est
de mesurer avec un profileur, mais c'est à mon avis très variable d'une
appli à l'autre.

Enfin, si tu fais du C++, la différence fondamentale entre la pile et le tas
n'est pas une question de vitesse d'allocation mais de sémantique de durée
de vie des objets : un objet sur la pile est détruit uand il sort du
"scope", pas sur le tas.

Arnaud
Avatar
Vincent Burel
"Arnaud Debaene" wrote in message
news:40842d9a$0$21161$
Cyrille Szymanski wrote:
> On 2004-04-19, roger_that <fabsk+ wrote:
>> Il est bien plus rapide d'"allouer" des buffers locaux sur la pile
>> que sur le tas, donc on a intéret à maximiser la taille dispo pour
>> la pile.
>
> C'est beaucoup plus rapide mais tant que la pile sera exécutable cela
> posera de gros problèmes au niveau sécurité. Il faut faire attention
> à ce que l'on fait. Un programme qui corrompt le tas c'est pas
> trop grave (voir plus loin) mais un programme qui corrompt la pile...
>
> Le gros problème sous Windows c'est les implémentations foireuses
> de malloc() &co., du genre :
Ce n'est pas Windows qui implémente malloc : c'est la CRT que tu utilises


:
celle de VC, celle de Borland ou autre... Tu penses à laquelle en
particulier?



générallement le malloc utilise un HeapAlloc ou un GlobalAlloc...
Après il est évident qu'un programmeur sait quand est-ce qu'il faut utiliser
ces fonctions ou pas :-)

Tu connais des implémentations d'allocateur généraliste (pas de taille


fixe,
etc...) qui font autrement sans obtenir rapidement une fragmentation
catastrophique?



Si la taille des élément reste petite (de 1 octet à quelque kilo) on peut
faire des gestion particulière sur un tas qui augment par pas de 500 kilo ou
5 Mega et qui autorise une défragmentation en mode Idle... mais ca c'est un
truc de programmeur évidemment :-)

Raison de performances pour le coup : appeler free sur un pointeur non


aloué
par malloc est un comportement indéfini :



généralement ca plante.

> Et c'est sans parler des techniques d'optimisation (buffer pool :
> allouer de la mémoire par avance et réutiliser les buffers...)
Bien sûr, c'est juste beaucoup de boulot en plus.



ben, une fois que c'est fait, c'est fait, et étant donné que ca fait partie
des premieres choses qu'on développe quand on commence l'informatique :
tableau dynamique, gestion de collection d'objet, gestion mémoire, gestion
de fichier, database, tri , dichotomie... bon je vais pas vous faire le
programme de première année de BTS... mais bon, si ta pas ces outils dans ta
musette , change de métier.

>> Bon, pour un x86 on risque d'être limité par l'étroitesse de l'espace
>> d'adressage, mais sur les AMD-64, j'imagine qu'on peut voir large
>> (par exemple allouer 4Go d'adressage pour la pile).
>
> Tu es limité par la taille de la mémoire virtuelle avant celle de
> l'es- pace d'adressage. Je rappelle que les processus sous windows
> s'exécutent tous dans un segment virtuel de 4Go. En gros si le tas
> peut pas faire 4Go, la pile le pourra pas plus.



1 Go pour le mapping DLL et un autre pour le mapping system... il reste même
pas 1Go pour le code je crois ,et la pile (par thread ) est par défaut de
l'ordre de qqc Mega... faut demander à AMCD qui poste plus ici :-)
Maintenant alloué de la mémoire dans la pile, à part pour quelque kilo de
variables locales... ca ne se pratique pas des masses...

VB
Avatar
Arnaud Debaene
Vincent Burel wrote:

celle de VC, celle de Borland ou autre... Tu penses à laquelle en
particulier?



générallement le malloc utilise un HeapAlloc ou un GlobalAlloc...
Après il est évident qu'un programmeur sait quand est-ce qu'il faut
utiliser ces fonctions ou pas :-)


Les CRT rajoutent leur propre mécanisme de cache/allocation par blocs par
dessus HeapAlloc, donc les problématiques de fragmentation/espace contigu
disponible/vitesse des allocations sontt complêtement différentes quand on
utilise une CRT.


Tu connais des implémentations d'allocateur généraliste (pas de
taille fixe, etc...) qui font autrement sans obtenir rapidement une
fragmentation catastrophique?



Si la taille des élément reste petite (de 1 octet à quelque kilo) on
peut faire des gestion particulière sur un tas qui augment par pas de
500 kilo ou 5 Mega et qui autorise une défragmentation en mode
Idle... mais ca c'est un truc de programmeur évidemment :-)


Tu penses à un garbage collector ou à autre chose?


Raison de performances pour le coup : appeler free sur un pointeur
non aloué par malloc est un comportement indéfini :



généralement ca plante.


Je parlais en termes de spécification de la norme C.


Et c'est sans parler des techniques d'optimisation (buffer pool :
allouer de la mémoire par avance et réutiliser les buffers...)


Bien sûr, c'est juste beaucoup de boulot en plus.



ben, une fois que c'est fait, c'est fait,


Non : il faut le faire à chaque programme.

et étant donné que ca fait
partie des premieres choses qu'on développe quand on commence
l'informatique : tableau dynamique, gestion de collection d'objet,
gestion mémoire, gestion de fichier, database, tri , dichotomie...
bon je vais pas vous faire le programme de première année de BTS...
mais bon, si ta pas ces outils dans ta musette , change de métier.


Tu es toujours aussi désagréable à chaque fois que tu réponds, oh grand
gourou de l'informatique?

Tu es limité par la taille de la mémoire virtuelle avant celle de
l'es- pace d'adressage. Je rappelle que les processus sous windows
s'exécutent tous dans un segment virtuel de 4Go. En gros si le tas
peut pas faire 4Go, la pile le pourra pas plus.





1 Go pour le mapping DLL et un autre pour le mapping system... il
reste même pas 1Go pour le code je crois


Tu crois mal : Il y a 2GO pour *toutes* les données du mode user : code,
tas, piles, TLS, données statiques, etc...

,et la pile (par thread )
est par défaut de l'ordre de qqc Mega...


Comme l'OP la dit, c'est (généralement) un paramètre du compilateur. C'est
en tout cas un champ dans l'en-tête des fichiers PE (SizeOfStackReserve et
StackOfSizeCommit dans IMAGE_OPTIONAL_HEADER). La valeur par défaut est 1MO.

Arnaud
Avatar
AMcD®
Arnaud Debaene wrote:

Tu es toujours aussi désagréable à chaque fois que tu réponds, oh
grand gourou de l'informatique?



Heu, t'es vraiment sûr que c'est pas péjoratif hein ?

--
AMcD®

http://arnold.mcdonald.free.fr/
Avatar
Arnaud Debaene
AMcD® wrote:
Arnaud Debaene wrote:

Tu es toujours aussi désagréable à chaque fois que tu réponds, oh
grand gourou de l'informatique?



Heu, t'es vraiment sûr que c'est pas péjoratif hein ?



LOL! On va dire que ca m'a échappé ;-)

Arnaud
Avatar
AMcD®
Arnaud Debaene wrote:

Tu crois mal : Il y a 2GO pour *toutes* les données du mode user :
code, tas, piles, TLS, données statiques, etc...



Toi aussi tu crois mal, cela dépend de ton OS et de quelques réglages :o).
Par exemple, via le célèbre /3GB, tu peux avoir 3GO d'espace d'adressage.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dngenlib/html/awewindata.asp

Comme l'OP la dit, c'est (généralement) un paramètre du compilateur.
C'est en tout cas un champ dans l'en-tête des fichiers PE
(SizeOfStackReserve et StackOfSizeCommit dans IMAGE_OPTIONAL_HEADER).
La valeur par défaut est 1MO.



Le champ important est surtout le premier, qui est la taille maximale pour
le thread. C'est une valeur 32 bits, donc 2^32 = 4GO.

--
AMcD®

http://arnold.mcdonald.free.fr/
Avatar
Cyrille Szymanski
On 2004-04-19, Arnaud Debaene wrote:
Le gros problème sous Windows c'est les implémentations foireuses
de malloc() &co., du genre :


Ce n'est pas Windows qui implémente malloc : c'est la CRT que tu utilises :



Où vois-tu que j'ai dit le contraire ?


celle de VC, celle de Borland ou autre... Tu penses à laquelle en
particulier?



Oui : celles de Borland et de lcc-win32. Pour VC je n'ai pas testé.


* pour allouer un bloc il faut généralement traverser tous ceux
du tas avant de trouver un espace libre, ce qui est cata-
strophique au niveau performance.


Tu connais des implémentations d'allocateur généraliste (pas de taille fixe,
etc...) qui font autrement sans obtenir rapidement une fragmentation
catastrophique?



Oui : celle de la lib C que j'ai sous FreeBSD par exemple.
http://www.freebsd.org/cgi/man.cgi?query=malloc&apropos=0&sektion=0&manpath=FreeBSD+5.2-RELEASE+and+Ports&format=html

Je rajoute une remarque : en parcourant toute la liste chaînée des
blocs on tue tous les effets bénéfiques de la pagination.

Après on s'étonne qu'un appel à malloc soit gourmand et on cherche
à bidouiller une autre solution qui généralement rend les programmes
moins stables.


Raison de performances pour le coup : appeler free sur un pointeur non aloué
par malloc est un comportement indéfini : à prtir de là, ajouter des tests
supplémentaires est certes une aide au débogage mais également un frein aux
perfs.



Oui, les implémentations que j'utilise permettent de passer d'un mode
debug avec garde fou à un mode release performant.

Dire "c'est normal que ça plante tu as appelé free() avec un mauvais
paramètre, c'est écrit dans la doc il n'y a rien à changer" quand on
sait le nombre de bogues que cela engendre moi j'appelle ça de la
mauvaise foi.


Rien qu'en changeant de routine malloc() &co. on y gagne.


Et en la remplacant par?



Une autre implémentation de malloc(), il en existe même certaines
capables de faire du gc. Ou alors en dernier recours, ta propre
implémentation.

Je ne vais pas citer ESR, cela a déjà été fait dans ce fil, mais
il ne faut pas se lancer tête baissée dans des optimisations
aléatoires.


--
cns
Avatar
Cyrille Szymanski
> ben, une fois que c'est fait, c'est fait, et étant donné que ca fait partie
des premieres choses qu'on développe quand on commence l'informatique :
tableau dynamique, gestion de collection d'objet, gestion mémoire, gestion
de fichier, database, tri , dichotomie... bon je vais pas vous faire le
programme de première année de BTS... mais bon, si ta pas ces outils dans ta
musette , change de métier.



Beaucoup de programmeurs "hobbyistes" n'ont malheureusement pas ça
dans leur musette et quand ils les ont (par ex ils ont pris la peine
de lire des bouts de la STL) ils les utilisent n'importe comment.


l'ordre de qqc Mega... faut demander à AMCD qui poste plus ici :-)



AMcD revieeeeeeens


Maintenant alloué de la mémoire dans la pile, à part pour quelque kilo de
variables locales... ca ne se pratique pas des masses...



J'ai un avis mitigé sur la question.

C'est vrai qu'une allocation sur la pile est plus rapide que sur le
tas, alors pour les variables locales, même si elles sont grandes,
ça vaut le coup.

Mais si on veut allouer dynamiquement de la mémoire sur la pile
il faut utiliser alloca(), et là c'est une source de problèmes car
cette mémoire est libérée quand on sort de la fonction. Alors
il faut vraiment savoir ce que l'on fait avant d'y mettre les doigts
sinon on risque de se faire pincer très fort.

--
cns
1 2