Pourquoi la taille de la pile en C # est-elle exactement de 1 Mo?

102

Les PC d'aujourd'hui ont une grande quantité de RAM physique, mais la taille de la pile de C # n'est que de 1 Mo pour les processus 32 bits et de 4 Mo pour les processus 64 bits ( capacité de pile en C # ).

Pourquoi la taille de la pile dans CLR est-elle encore si limitée?

Et pourquoi est-ce exactement 1 Mo (4 Mo) (et non 2 Mo ou 512 Ko)? Pourquoi a-t-il été décidé d'utiliser ces montants?

Je m'intéresse aux considérations et aux raisons derrière cette décision .

Nikolay Kostov
la source
6
La taille de pile par défaut pour les processus 64 bits est de 4 Mo, elle est de 1 Mo pour les processus 32 bits. Vous pouvez modifier la taille de la pile des threads principaux en modifiant la valeur dans son en-tête PE. Vous pouvez également spécifier la taille de la pile en utilisant la surcharge de droite du Threadconstructeur. MAIS, cela soulève la question, pourquoi avez-vous besoin d'une pile plus grande?
Yuval Itzchakov
2
Merci, édité. :) La question n'est pas de savoir comment utiliser une plus grande taille de pile, mais pourquoi la taille de pile est décidée à 1 Mo (4 Mo) .
Nikolay Kostov
8
Parce que chaque thread aura cette taille de pile par défaut, et la plupart des threads n'en ont pas besoin de beaucoup. Je viens de démarrer mon PC et le système exécute actuellement 1200 threads. Maintenant, faites le calcul;)
Lucas Trzesniewski
2
@LucasTrzesniewski Non seulement cela, il doit être contagieux en mémoire . Notez que plus la taille de la pile est grande, moins votre processus peut créer de threads dans son espace d'adressage virtuel.
Yuval Itzchakov
Pas sûr de "exactement" 1 Mo: sur mon Windows 8.1, une application console .NET Core 3.1 a 1572864une taille de pile par défaut en octets (récupérée à l'aide de l'API Win32 GetCurrentThreadStackLimits). Je suis capable d' stackallocenviron 1500000octets sans StackOverflowException.
George Chakhidze le

Réponses:

210

entrez la description de l'image ici

Vous regardez le gars qui a fait ce choix. David Cutler et son équipe ont sélectionné un mégaoctet comme taille de pile par défaut. Rien à voir avec .NET ou C #, cela a été cloué lors de la création de Windows NT. Un mégaoctet est ce qu'il choisit lorsque l'en-tête EXE d'un programme ou l'appel winapi CreateThread () ne spécifie pas explicitement la taille de la pile. Ce qui est la manière normale, presque tous les programmeurs laissent au système d'exploitation le soin de choisir la taille.

Ce choix est probablement antérieur à la conception de Windows NT, l'histoire est bien trop trouble à ce sujet. Ce serait bien si Cutler écrivait un livre à ce sujet, mais il n'a jamais été écrivain. Il a été extrêmement influent sur le fonctionnement des ordinateurs. Sa première conception de système d'exploitation était RSX-11M, un système d'exploitation 16 bits pour les ordinateurs DEC (Digital Equipment Corporation). Il a fortement influencé le CP / M de Gary Kildall, le premier système d'exploitation décent pour les microprocesseurs 8 bits. Ce qui a fortement influencé MS-DOS.

Sa conception suivante était VMS, un système d'exploitation pour processeurs 32 bits avec prise en charge de la mémoire virtuelle. Connait un grand succès. Son prochain a été annulé par DEC au moment où la société a commencé à se désintégrer, ne pouvant pas rivaliser avec du matériel informatique bon marché. Cue Microsoft, ils lui ont fait une offre qu'il ne pouvait pas refuser. Beaucoup de ses collègues l'ont également rejoint. Ils ont travaillé sur VMS v2, mieux connu sous le nom de Windows NT. DEC s'est énervé à ce sujet, l'argent a changé de mains pour le régler. Je ne sais pas si VMS a déjà choisi un mégaoctet, je ne connais que RSX-11 assez bien. Ce n'est pas improbable.

Assez d'histoire. Un mégaoctet, c'est beaucoup , un vrai thread consomme rarement plus de quelques poignées de kilo-octets. Donc, un mégaoctet est en fait plutôt un gaspillage. C'est cependant le genre de gaspillage que vous pouvez vous permettre sur un système d'exploitation de mémoire virtuelle paginée à la demande, ce mégaoctet n'est que de la mémoire virtuelle . Juste des nombres pour le processeur, un pour chaque 4096 octets. Vous n'utilisez jamais réellement la mémoire physique, la RAM de la machine, jusqu'à ce que vous l'adressiez réellement.

Il est extrêmement excessif dans un programme .NET car la taille d'un mégaoctet a été initialement choisie pour accueillir les programmes natifs. Qui ont tendance à créer de grands cadres de pile, stockant également des chaînes et des tampons (tableaux) sur la pile. Tristement célèbre pour être un vecteur d'attaque de malware, un débordement de tampon peut manipuler le programme avec des données. Ce n'est pas la façon dont les programmes .NET fonctionnent, les chaînes et les tableaux sont alloués sur le tas GC et l'indexation est vérifiée. La seule façon d'allouer de l'espace sur la pile avec C # est d' utiliser le mot clé unsafe stackalloc .

La seule utilisation non triviale de la pile dans .NET est la gigue. Il utilise la pile de votre thread pour compiler juste à temps MSIL en code machine. Je n'ai jamais vu ni vérifié l'espace dont il a besoin, cela dépend plutôt de la nature du code et si l'optimiseur est activé ou non, mais quelques dizaines de kilo-octets sont une estimation approximative. Ce qui est autrement ainsi que ce site Web tire son nom, un débordement de pile dans un programme .NET est assez fatal. Il n'y a pas assez d'espace disponible (moins de 3 kilo-octets) pour toujours JIT de manière fiable tout code qui tente d'attraper l'exception. Kaboom au bureau est la seule option.

Dernier point mais non le moindre, un programme .NET fait quelque chose d'assez improductif avec la pile. Le CLR validera la pile d'un thread. C'est un mot coûteux qui signifie qu'il ne réserve pas seulement la taille de la pile, il s'assure également que l'espace est réservé dans le fichier d'échange du système d'exploitation afin que la pile puisse toujours être échangée si nécessaire. Le fait de ne pas s'engager est une erreur fatale et met fin à un programme sans condition. Cela ne se produit que sur une machine avec très peu de RAM qui exécute trop de processus, une telle machine se sera transformée en mélasse avant que les programmes ne commencent à mourir. Un problème possible il y a plus de 15 ans, pas aujourd'hui. Les programmeurs qui règlent leur programme pour agir comme une voiture de course F1 utilisent l' <disableCommitThreadStack>élément dans leur fichier .config.

Fwiw, Cutler n'a pas arrêté de concevoir des systèmes d'exploitation. Cette photo a été prise alors qu'il travaillait sur Azure.


Mise à jour, j'ai remarqué que .NET ne valide plus la pile. Je ne sais pas exactement quand ni pourquoi cela s'est produit, cela fait trop longtemps que j'ai vérifié. Je suppose que ce changement de conception s'est produit quelque part autour de .NET 4.5. Changement assez sensible.

Hans Passant
la source
3
écrit à votre commentaire The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword.- Les variables locales, par exemple, sont-elles intdéclarées dans une méthode non stockée sur la pile? Je pense qu'ils sont.
RBT
2
D'accord. Maintenant, je comprends qu'un stack-frame n'est pas le seul choix de stockage pour les variables locales d'une fonction. Il peut être stocké sur un cadre de pile comme suggéré dans l'un de vos puces. Très éclairant Hans. Je ne peux pas dire assez de merci pour avoir écrit des articles aussi perspicaces. Honnêtement, la pile est une si grande abstraction pour la programmation en général, juste pour éviter une complexité inutile.
RBT
Description très détaillée @Hans. Je me demandais juste quelle est la valeur minimale possible maxStackSized'un thread? Je n'ai pas pu le trouver sur [MSDN] ( msdn.microsoft.com/en-us/library/5cykbwz4(v=vs.110).aspx ). Sur la base de vos commentaires, il semble que l'utilisation de la pile est minimale et que je peux utiliser la plus petite valeur pour accueillir le maximum de threads possibles. Merci.
MKR
1
@KFL: Vous pouvez facilement répondre à votre question en l'essayant!
Eric Lippert
1
Si le comportement par défaut est de ne plus valider la pile, alors ce fichier de démarque doit être édité github.com/dotnet/docs/blob/master/docs/framework/…
John Stewien
5

La taille de pile réservée par défaut est spécifiée par l'éditeur de liens et elle peut être remplacée par les développeurs en modifiant la valeur PE au moment de la liaison ou pour un thread individuel en spécifiant le dwStackSizeparamètre de la CreateThreadfonction WinAPI.

Si vous créez un thread avec la taille de pile initiale supérieure ou égale à la taille de pile par défaut, il est arrondi au multiple de 1 Mo le plus proche.

Pourquoi la valeur est-elle égale à 1 Mo pour les processus 32 bits et 4 Mo pour 64 bits? Je pense que vous devriez demander aux développeurs, qui ont conçu Windows, ou attendre que quelqu'un d'entre eux réponde à votre question.

Mark Russinovich le sait probablement et vous pouvez le contacter . Vous pouvez peut-être trouver ces informations dans ses livres Windows Internals antérieurs à la sixième édition, qui décrivent moins d'informations sur les piles que dans son article . Ou peut-être que Raymond Chen connaît des raisons puisqu'il écrit des choses intéressantes sur les composants internes de Windows et son histoire. Il peut également répondre à votre question, mais vous devriez publier une suggestion dans la boîte à suggestions .

Mais pour le moment, je vais essayer d'expliquer quelques raisons probables pour lesquelles Microsoft a choisi ces valeurs en utilisant les blogs MSDN, Mark's et Raymond.

Les valeurs par défaut ont ces valeurs probablement parce que dans les premiers temps les PC étaient lents et l'allocation de mémoire sur la pile était beaucoup plus rapide que l'allocation de mémoire dans le tas. Et comme les allocations de pile étaient beaucoup moins chères, elles étaient utilisées, mais cela nécessitait une taille de pile plus grande.

La valeur était donc la taille de pile réservée optimale pour la plupart des applications. C'est optimal car permet de faire beaucoup d'appels imbriqués et d'allouer de la mémoire sur la pile pour passer des structures aux fonctions appelantes. En même temps, cela permet de créer beaucoup de threads.

De nos jours, ces valeurs sont principalement utilisées pour la rétrocompatibilité, car les structures qui sont passées en paramètres aux fonctions WinAPI sont toujours allouées sur la pile. Mais si vous n'utilisez pas d'allocations de pile, l'utilisation de la pile d'un thread sera nettement inférieure à la valeur par défaut de 1 Mo et c'est un gaspillage comme l'a mentionné Hans Passant. Et pour éviter cela, le système d'exploitation ne valide que la première page de la pile (4 Ko), si autre n'est pas spécifié dans l'en-tête PE de l'application. D'autres pages sont attribuées à la demande.

Certaines applications remplacent l'espace d'adressage réservé et s'engagent initialement à optimiser l'utilisation de la mémoire. Par exemple, la taille de pile maximale du thread d'un processus natif IIS est de 256 Ko ( KB932909 ). Et cette diminution des valeurs par défaut est recommandée par Microsoft:

Il est préférable de choisir une taille de pile aussi petite que possible et de valider la pile qui est nécessaire pour que le fil ou la fibre fonctionne de manière fiable. Chaque page réservée à la pile ne peut être utilisée à aucune autre fin.

Sources:

  1. Taille de la pile de threads (Microsoft Docs)
  2. Repousser les limites de Windows: processus et threads (Mark Russinovich)
  3. Par défaut, la taille maximale de la pile d'un thread créé dans un processus IIS natif est de 256 Ko (KB932909)
Yoh Deadfall
la source
Si je veux une plus grande taille de pile, je peux la définir ( atalasoft.com/cs/blogs/rickm/archive/2008/04/22/… ). Je veux connaître les considérations et les raisons de cette décision.
Nikolay Kostov
2
D'accord. Maintenant je vous comprends :) La taille de pile par défaut doit être optimale (voir le commentaire @Lucas Trzesniewski) et doit être arrondie au multiple le plus proche de la granularité d'allocation. Si la taille de pile spécifiée est supérieure à la taille de pile par défaut, elle est arrondie au multiple de 1 Mo le plus proche. Microsoft a donc choisi ces tailles comme tailles de pile par défaut pour toutes les applications en mode utilisateur. Et il n'y a pas d'autres raisons.
Yoh Deadfall
Des sources? Une documentation? :)
Nikolay Kostov
@Yoh lien intéressant. Vous devriez résumer cela dans votre réponse.
Lucas Trzesniewski