Ce qui est plus rapide: l'allocation de Pile ou le Tas de répartition
Cette question peut sembler assez élémentaire, mais c'est un débat que j'ai eu avec un autre développeur qui travaillent avec moi.
J'ai été en prenant soin de pile allouer des choses où j'ai pu, au lieu de tas de leur attribution. Il était en train de parler de moi et regarder par-dessus mon épaule et a fait observer qu'il n'était pas nécessaire parce qu'ils sont de la même performance sage.
J'ai toujours été sous l'impression que la croissance de la pile de la constante de temps, et de l'allocation de tas de performance dépendait de la complexité actuelle du tas pour les deux allocation (pour trouver un trou de la bonne taille) et de l'allocation (l'effondrement des trous afin de réduire la fragmentation, comme de nombreux standard de la bibliothèque implémentations de prendre le temps de le faire pendant supprime si je ne me trompe pas).
Ce qui me frappe comme quelque chose qui serait probablement très dépendant du compilateur. Pour ce projet en particulier, je suis à l'aide d'un Metrowerks compilateur pour le PPC architecture. Aperçu sur cette combinaison serait la plus utile, mais en général, pour GCC, et MSVC++, ce qui est le cas? Est d'allocation de tas pas aussi performante que l'allocation de pile? Il n'y a pas de différence? Ou sont les différences afin de minute, il devient inutile de micro-optimisation.
- pourquoi ne pas simplement remplacer les vides e; avec quelque chose comme int j=i; qui permettrait de faire en sorte que l'allocation de pile n'a lieu.
- l'allocation de pile est beaucoup plus rapide.
- Je sais que c'est assez ancien, mais il serait agréable de voir certains C/C++ extraits montrant les différents types de répartition.
- Votre vache droits est terriblement ignorants, mais le plus important qu'il est dangereux parce qu'il fait autorité réclamations à propos de choses qu'il est terriblement ignorants à ce sujet. D'accise ces personnes de votre équipe le plus rapidement possible.
- Notez que le tas est généralement beaucoup plus grande que la pile. Si vous êtes affecté de grandes quantités de données, vous avez vraiment de le mettre sur le tas, ou bien changer la taille de la pile de l'OS.
- Toutes les optimisations sont, à moins d'avoir des repères ou de la complexité des arguments prouvant le contraire, par défaut, inutile de micro-optimisations.
- Je me demande si votre collègue a surtout Java ou C# de l'expérience. Dans ces langues, presque tout est allouées sur la pile sous le capot, ce qui pourrait conduire à de telles hypothèses.
- Pertinents (pour n'importe quelle langue): Comment fonctionne la pile de travail en assembleur?. Quand on sait ce que la pile c'est que c'est assez évident, il n'existe aucun moyen d'allocation de tas peut être plus rapide.
Vous devez vous connecter pour publier un commentaire.
Allocation de pile est beaucoup plus rapide puisqu'il n'a vraiment est de déplacer le pointeur de pile.
À l'aide de pools de mémoire, vous pouvez obtenir des performances comparables hors de l'allocation de tas, mais qui vient avec une légère ajouté de la complexité et de ses propres maux de tête.
Aussi, pile vs tas n'est pas seulement un facteur de performance; il dit aussi beaucoup sur la durée de vie prévue des objets.
Pile est beaucoup plus rapide. Littéralement n'en utilise qu'une seule instruction sur la plupart des architectures, dans la plupart des cas, par exemple, sur x86:
(Qui se déplace le pointeur de pile vers le bas par 0x10 octets et donc "alloue" ces octets pour une utilisation par une variable.)
Bien sûr, la taille de tapis est très, très limitée, comme vous allez rapidement découvrir si vous abusez de l'allocation de pile ou essayer de faire de la récursivité 🙂
Aussi, il y a peu de raison pour optimiser les performances de code qui n'est pas vérifiable en a besoin, comme démontré par le profilage. "L'optimisation prématurée" pose souvent plus de problèmes qu'elle en vaut la peine.
Ma règle d'or: si je sais que je vais avoir besoin de quelques données au moment de la compilation, et c'est en vertu de quelques centaines d'octets de taille, je pile-l'attribuer. Sinon je tas attribuer.
leave
instruction.Honnêtement, il est trivial d'écrire un programme pour comparer les performances:
Il est dit que un idiot de cohérence est le lutin de petits esprits. Apparemment, l'optimisation des compilateurs sont les lutins de nombreux programmeurs de l'esprit. Cette discussion utilisé pour être à la base de la réponse, mais les gens ne peuvent apparemment pas être pris la peine de lire de loin, donc je suis en déplacement jusqu'ici pour éviter de se faire des questions que j'ai déjà répondu.
Un compilateur optimisant peut remarquer que ce code ne fait rien, et peut optimiser tout de suite. C'est l'optimiseur de faire des trucs comme ça, et la lutte contre l'optimiseur est sans issue.
Je recommande la compilation de ce code avec l'optimisation éteint car il n'y a pas de bonne façon de tromper tous optimiseur actuellement en cours d'utilisation ou qui sera utilisé dans l'avenir.
Quelqu'un qui fait de la optimizer, puis se plaint de combat qu'il doit être soumis au ridicule public.
Si je souciait une précision à la nanoseconde je ne voudrais pas utiliser
std::clock()
. Si je voulais publier les résultats d'une thèse de doctorat, je voudrais faire une plus grosse affaire à ce sujet, et je serais probablement comparer GCC, je vais avoir/Ten15, LLVM, Watcom, Borland, Visual C++, Numérique de Mars, de la CPI et d'autres compilateurs. Comme il est, allocation de tas prend des centaines de fois plus que l'allocation de pile, et je ne vois pas quelque chose d'utile à étudier la question plus avant.L'optimiseur a pour mission de débarrasser le code je suis en essais. Je ne vois pas de raison de dire que l'optimiseur à exécuter, puis essayer de tromper l'optimiseur en a pas en fait de l'optimisation. Mais si j'ai vu de la valeur en faisant cela, je ferais un ou plusieurs des éléments suivants:
Ajouter un membre de données de
empty
, et accéder à ses données, membre de la boucle; mais si je n'avais jamais lu du membre de données de l'optimiseur peut faire de constantes et de supprimer la boucle; si je ne jamais écrire pour le membre de données, l'optimiseur peut passer tous les mais la dernière itération de la boucle. En outre, la question n'était pas "pile de répartition et d'accès aux données vs tas de répartition et d'accès aux données."Déclarer
e
volatile
, maisvolatile
est souvent compilées de manière incorrecte (PDF).Prendre l'adresse de
e
l'intérieur de la boucle (et peut-être l'attribuer à une variable qui est déclaréeextern
et définie dans un autre fichier). Mais même dans ce cas, le compilateur peut remarquer que, sur la pile au moins --e
seront toujours attribués à la même adresse mémoire, et puis faire de constantes comme dans (1) ci-dessus. Je reçois toutes les itérations de la boucle, mais l'objet n'est jamais réellement allouée.Au-delà de l'évident, ce test est erronée, car elle permet de mesurer à la fois l'allocation et la libération, et la question d'origine ne m'a pas demandé de libération de la mémoire. Bien sûr, les variables allouées sur la pile sont automatiquement libéré à la fin de leur portée, de sorte de ne pas appeler
delete
serait (1) biaiser les chiffres (pile de libération de la mémoire est inclus dans les chiffres sur l'allocation de pile, il est donc juste de mesurer tas de libération de la mémoire) et (2) la cause d'un assez mauvais souvenir de la fuite, à moins que nous de conserver une référence à la nouvelle pointeur et appeldelete
après nous avons eu notre mesure du temps.Sur ma machine, à l'aide de g++ 3.4.4 sur Windows, j'obtiens un "0 tops d'horloge" à la fois la pile et le tas de l'allocation de rien de moins que de 100000 allocations, et même alors, j'ai "0 tops d'horloge" pour l'allocation de pile et "15 ticks" pour l'allocation de tas. Lorsque je mesure 10 000 000 d'allocations, l'allocation de pile prend le 31 tops d'horloge et de tas d'allocation prend 1562 tops d'horloge.
Oui, un compilateur optimisant peut éluder créer le vide d'objets. Si je comprends bien, il peut éluder l'ensemble de la première boucle. Quand j'ai heurté les itérations de 10 000 000 allocation de pile pris 31 tops d'horloge et d'allocation de tas a pris 1562 tops d'horloge. Je pense qu'il est sûr de dire que sans le dire à g++ pour optimiser l'exécutable, g++ n'a pas éluder les constructeurs.
Dans les années depuis que j'ai écrit cela, la préférence sur Stack Overflow a été de performance par l'optimisation des builds. En général, je pense que c'est correct. Cependant, je pense toujours que c'est idiot de demander au compilateur d'optimiser le code, quand en fait, vous ne voulez pas que le code optimisé. Il me frappe comme étant très similaire à payer un supplément pour service de voiturier, mais refusant de remettre les clés. Dans ce cas particulier, je ne veux pas que l'optimiseur en cours d'exécution.
À l'aide d'une version légèrement modifiée de l'indice de référence (à l'adresse de point valide que l'original n'a pas allouer quelque chose sur la pile à chaque passage dans la boucle) et de la compilation sans optimisations, mais en les reliant à la libération des bibliothèques (à l'adresse valide point que nous ne voulons pas comprendre tout ralentissement causé par les reliant à des bibliothèques de débogage):
affiche:
sur mon système lorsqu'il est compilé avec la ligne de commande
cl foo.cc /Od /MT /EHsc
.Vous ne pouvez pas d'accord avec ma démarche pour l'obtention d'un non-optimisé construire. C'est très bien: n'hésitez pas à modifier l'indice de référence autant que vous le souhaitez. Quand j'allume l'optimisation, j'obtiens:
Pas parce que l'allocation de pile est en fait instantané, mais parce que toute demi-décent compilateur peut remarquer que
on_stack
ne pas faire quelque chose d'utile et peut être optimisé à l'écart. GCC sur mon Linux ordinateur portable remarque également queon_heap
ne pas faire quelque chose d'utile, et optimise l'éloignant ainsi:stack allocation took 0.15354 seconds, heap allocation took 0.834044 seconds
avec-O0
ensemble, rendre Linux allocation de tas plus lent sur un facteur d'environ 5,5 sur ma machine en particulier.cl.exe
( msdn.microsoft.com/en-us/library/19z1t1wy.aspx ), je crois qu'il est possible de désactiver les optimisations (/Od
), tout en les reliant avec la version runtime (/MT
).alloca
100 fois.[noinline]
sur les fonctions. Vous pouvez avoir besoinvolatile
. Généralement, vous avez pour vérifier l'asm à assurez-vous que vous avez obtenu ce que vous vouliez.gcc -O0
sont ridicules. Il n'est même pas proche d'un linéaire de ralentissement qui affecte tous les code également. Voir Ajouter un redondante affectation des vitesses code lors de la compilation sans optimisation pour le cas où elle conduit à des super-bizarre résultats en raison de stocker de transfert de latence bizarreries sur Sandybridge-famille.Une chose intéressante, j'ai appris à propos de la Pile vs Allocation de Tas sur la Xbox 360 Xenon processeur, ce qui peut également s'appliquer à d'autres systèmes multicœurs, c'est que l'allocation sur le Tas provoque une Section Critique entrer pour mettre fin à toutes les autres cœurs, afin que l'alloc n'entre pas en conflit. Ainsi, dans une boucle serrée, l'Allocation de Pile était la voie à suivre pour fixe la taille des tableaux, car elle empêchait les étals.
Cela peut être un autre facteur à considérer si vous êtes codant pour le multicœur/multiproc, dans votre pile de répartition ne seront visibles que par la base en cours d'exécution de votre fonction de l'étendue, et qui n'affectera pas les autres cores/Cpu.
Vous pouvez créer un segment de l'allocateur pour certaines dimensions des objets qui est très performant. Cependant, la général tas allocateur n'est pas particulièrement performant.
Aussi je suis d'accord avec Torbjörn Gyllebring sur la durée de vie prévue des objets. Bon point!
Je ne pense pas que l'allocation de pile et le tas de répartition sont généralement interchangeables. J'espère aussi que la performance de chacun d'eux est suffisant pour un usage général.
Je recommanderais fortement pour les petits objets, celle qui est la plus adaptée à l'étendue de la répartition. Pour les gros articles, le tas est probablement nécessaire.
Sur les systèmes d'exploitation 32 bits qui ont plusieurs threads, la pile est souvent assez limité (bien que, généralement, au moins un peu de mo), car l'espace d'adressage doit être découpé et tôt ou tard, une pile de thread sera exécuté dans un autre. Sur single threaded systèmes (Linux glibc mono-thread de toute façon) la limitation est beaucoup moins parce que la pile peut que croître et de se développer.
Sur les systèmes d'exploitation 64 bits il y a assez d'espace d'adressage de faire des piles de threads assez grand.
Généralement de l'allocation de pile juste consiste en la soustraction à partir du registre de pointeur de pile. C'est des tonnes plus rapide que de chercher un segment.
Parfois l'allocation de pile nécessite l'ajout d'une page(s) de la mémoire virtuelle. L'ajout d'une nouvelle page de mise à zéro de la mémoire ne nécessite pas la lecture d'une page à partir d'un disque, donc en général c'est toujours des tonnes plus rapide que de chercher un segment (surtout si une partie de ce segment a été paginé trop). Dans une situation rare, et vous pouvez construire un tel exemple, suffisamment d'espace est disponible dans le cadre du tas qui est déjà dans la mémoire vive, mais l'allocation d'une nouvelle page pour la pile doit attendre une autre page pour être écrits sur le disque. Dans cette situation rare, le tas est plus rapide.
Outre les ordres de grandeur des performances de l'avantage sur les tas d'allocation, l'allocation de pile est préférable pour de longues serveur d'applications. Même les mieux gérées au tas, éventuellement, obtenir tellement fragmentée que l'application, les performances se dégradent.
Une pile a une capacité limitée, tout un tas n'est pas. Typique de la pile d'un processus ou thread est d'environ 8K. Vous ne pouvez pas modifier la taille une fois qu'il est attribué.
Une pile variable suit les règles de portée, tandis qu'un segment de mémoire on n'a pas. Si votre pointeur d'instruction va au-delà d'une fonction, toutes les nouvelles variables associées à la fonction aller loin.
Le plus important de tous, vous ne pouvez pas prédire l'ensemble de la fonction appel de la chaîne à l'avance. Un peu plus de 200 octets d'allocation de votre part peut faire un débordement de pile. Ceci est particulièrement important si vous êtes à la rédaction d'une bibliothèque, pas une application.
Je pense que la durée de vie est essentielle, et si la chose étant alloué doit être construit de manière complexe. Par exemple, dans l'opération pilotée par la modélisation, généralement, vous devez le remplir et le transmettre dans la structure de la transaction avec un tas de champs de fonctions d'exploitation. Regardez l'OSCI SystemC TLM-2.0 standard pour un exemple.
L'allocation sur la pile à proximité de l'appel à l'opération tend à causer d'énormes frais généraux, comme la construction est cher. La bonne façon il ya d'allouer sur le tas et la réutilisation de la transaction, les objets, soit par la mise en commun ou d'une simple politique comme "ce module ne nécessite qu'un objet de transaction à jamais".
C'est plusieurs fois plus rapide que l'allocation de l'objet sur chaque opération d'appel.
La raison en est simplement que l'objet a un coûteux, la construction et d'une assez longue durée de vie utile.
Je dirais: essayer les deux et voir ce qui fonctionne le mieux dans votre cas, car il peut vraiment dépendre sur le comportement de votre code.
Probablement le plus grand problème de l'allocation de tas rapport à l'allocation de pile, est que l'allocation de tas dans le cas général est une surabondance de l'opération, et vous ne pouvez donc pas l'utiliser où le calendrier est un problème.
Pour d'autres applications où le calendrier n'est pas un problème, il ne peut pas d'importance, mais si vous tas allouer beaucoup, cela va affecter la vitesse d'exécution. Essayez toujours d'utiliser la pile pour de courte durée et souvent la mémoire allouée (par exemple dans les boucles), et aussi longtemps que possible - ne tas d'allocation au démarrage de l'application.
Ce n'est pas seulement l'allocation de pile qui est plus rapide. Vous gagnez également beaucoup sur l'utilisation de variables de pile. Ils ont la meilleure localité de référence. Et enfin, la désallocation est beaucoup moins cher aussi.
Allocation de pile sera presque toujours être aussi rapide ou plus rapide que l'allocation de tas, bien qu'il soit certainement possible pour un segment de l'allocateur de simplement utiliser une pile d'allocation basée sur la technique.
Cependant, il ya plus de problèmes lorsqu'ils traitent de la performance globale de la pile vs tas en fonction de répartition (ou légèrement meilleures conditions, locale et ceux de l'extérieur de l'allocation). Généralement, des tas (externe) de l'allocation est lent, car il s'agit de différents types d'allocations et les schémas de répartition. La réduction de la portée de l'allocateur que vous utilisez (le rendant locale de l'algorithme/code) aura tendance à augmenter les performances sans changements majeurs. L'ajout de mieux structurer vos schémas de répartition, par exemple, en forçant un PRINCIPE de la commande sur l'allocation et la libération des paires peut aussi améliorer votre allocateur de performances en utilisant l'allocateur de plus simple et de manière plus structurée. Ou, vous pouvez utiliser ou d'écrire un allocateur à l'écoute de votre allocation de modèle; la plupart des programmes d'allouer quelques discrètes tailles fréquemment, de sorte qu'un segment qui est basé sur un tampon de quelques fixe (de préférence connu) tailles va effectuer très bien. Windows utilise sa faible fragmentation du tas pour cette raison.
Sur l'autre main, une pile d'allocation fondée sur un 32 bits de la mémoire est également périlleuse si vous avez un trop grand nombre de threads. Les piles ont besoin d'une mémoire contiguë, de sorte que le plus de threads que vous avez, le plus d'espace d'adressage virtuel, vous aurez besoin pour eux de courir sans un débordement de pile. Ce ne sera pas un problème (pour l'instant) avec la version 64 bits, mais il peut certainement faire des ravages dans de longues programmes en cours d'exécution avec beaucoup de threads. En cours d'exécution hors de l'espace d'adresse virtuelle en raison de la fragmentation est toujours une douleur à traiter.
Allocation de pile est un couple instructions alors que le plus rapide rtos tas allocateur de connu pour moi (TLSF) utilisent, en moyenne, de l'ordre de 150 instructions. Aussi empiler les allocations ne nécessitent pas un verrou, car ils utilisent thread local storage qui est une autre grosse performance de gagner. Donc pile allouer de 2 à 3 ordres de grandeur plus rapide en fonction de la façon dont beaucoup multithread votre environnement.
En général d'allocation de tas est votre dernier recours, si vous vous souciez de la performance. Viable entre l'option peut être d'un montant fixe de l'allocateur qui est également à seulement quelques instructions et a très peu par l'allocation des frais généraux ainsi il est idéal pour les petites fixe la taille des objets. Sur le revers de la médaille il ne fonctionne qu'avec la taille fixe des objets, n'est pas intrinsèquement thread-safe et a bloquer les problèmes de fragmentation.
Il y a un point de vue général sur de telles optimisations.
L'optimisation que vous obtenez est proportionnelle à la quantité de fois que le compteur de programme est dans le code.
Si vous goûtez le compteur de programme, vous découvrirez où il passe son temps, et c'est en général dans une petite partie du code, et souvent dans les routines de la bibliothèque vous n'avez aucun contrôle sur.
Seulement si vous avez du passer beaucoup de temps dans le tas-répartition de vos objets, il sera nettement plus rapide à la pile de les affecter.
Comme d'autres l'ont dit, l'allocation de pile est généralement beaucoup plus rapide.
Toutefois, si vos objets sont chers pour la copie, l'allocation sur la pile peut entraîner un gain de performance énorme hit plus tard, lorsque vous utilisez les objets si vous n'êtes pas prudent.
Par exemple, si vous allouer quelque chose sur la pile, puis le mettre dans un conteneur, il aurait été préférable d'allouer sur le tas et de stocker le pointeur dans le conteneur (par exemple, avec un std::shared_ptr<>). La même chose est vraie si vous êtes de passage ou de retour des objets par valeur, et d'autres scénarios similaires.
Le point est que, bien que l'allocation de pile est généralement meilleure que l'allocation de tas dans de nombreux cas, parfois, si vous sortez de votre façon d'empiler allouer quand il n'est pas le meilleur ajustement du modèle de calcul, il peut causer plus de problèmes qu'elle n'en résout.
Ce serait comme ça en asm. Lorsque vous êtes dans
func
, lef1
et pointeurf2
a été alloué sur la pile (stockage automatisé). Et par la manière, Foof1(a1)
a pas d'instruction des effets sur le pointeur de pile (esp
),Il a été alloué, sifunc
veut obtenir le membref1
, c'est l'instruction est quelque chose comme ceci:lea ecx [ebp+f1], call Foo::SomeFunc()
. Une autre chose de la pile allouer peut rendre quelqu'un pense que la mémoire est quelque chose commeFIFO
, leFIFO
vient de se passer quand vous allez dans une fonction, si vous êtes dans la fonction et allouer quelque chose commeint i = 0
, il n'y pas de pression qui s'est passé.Il a été mentionné avant que l'allocation de pile est simplement en déplaçant le pointeur de pile, c'est une seule et même instruction sur la plupart des architectures. Comparez cela à ce que généralement qui se passe dans le cas de l'allocation de tas.
Le système d'exploitation gère des portions de mémoire libre comme une liste liée avec les données de la charge utile comprenant le pointeur à l'adresse de départ de la partie libre et de la taille de la partie libre. Pour allouer de X octets de mémoire, la liste des liens est traversé, et chaque note est visité dans l'ordre, de la vérification pour voir si sa taille est d'au moins X. Lorsqu'une partie avec la taille de P >= X est trouvé, P est scindé en deux parties avec des tailles X et P-X. La liste est mise à jour et le pointeur vers la première partie est renvoyée.
Comme vous pouvez le voir, tas de répartition dépend peut facteurs, comme la quantité de mémoire que vous demandez, de fragmentation de la mémoire et ainsi de suite.
En général, l'allocation de pile est plus rapide que l'allocation de tas, comme mentionné par presque toutes les réponses ci-dessus. Une pile push /pop est O(1), tandis que d'allouer ou libérer d'un segment de mémoire peut exiger d'un pied de précédentes allocations. Cependant vous ne devriez pas généralement attribué au serrés, les performances à forte intensité de boucles, de sorte que le choix viennent généralement en bas à d'autres facteurs.
Il pourrait être bon de faire cette distinction: vous pouvez utiliser une pile allocateur" sur le tas. Strictement parlant, je prends l'allocation de pile à dire la méthode de répartition plutôt qu'à l'emplacement de l'allocation. Si vous êtes à l'allocation de beaucoup de choses sur la pile de programme, qui pourrait être mauvais pour une variété de raisons. D'autre part, à l'aide d'une pile méthode pour allouer sur le tas lorsque cela est possible est le meilleur choix que vous pouvez faire pour une méthode de répartition.
Puisque vous avez mentionné Metrowerks et le PPC, je suppose que tu veux dire Wii. Dans ce cas, la mémoire est à une prime, et à l'aide d'une pile méthode de répartition, dans la mesure du possible des garanties que vous ne perdez pas la mémoire sur des fragments. Bien sûr, cela demande beaucoup plus de soins que la "normale" tas de méthodes de répartition. Il est sage d'évaluer les avantages et les inconvénients de chaque situation.
Remarque que les considérations qui ne sont généralement pas sur la vitesse et les performances lors du choix de la pile contre allocation de tas. La pile agit comme une pile, ce qui signifie qu'il est bien adapté pour les poussant des blocs et à éclater de nouveau, dernier entré, premier sorti. L'exécution des procédures est également similaire à la pile, la dernière procédure de saisie est le premier à être sorti. Dans la plupart des langages de programmation, toutes les variables nécessaires dans une procédure ne sera visible que lors de l'exécution de la procédure, ainsi, ils sont poussés à la signature d'une procédure et a sauté hors de la pile lors de la sortie ou de retour.
Maintenant pour un exemple de cas où la pile ne peut pas être utilisé:
Si vous allouer de la mémoire dans la procédure S et le mettre sur la pile, puis la sortie S, les données allouées sera sauté hors de la pile. Mais la variable x dans P a également souligné que les données, donc x est maintenant pointant vers un endroit sous le pointeur de pile (à supposer pile grandit vers le bas) avec un contenu inconnu. Le contenu peut encore être là, si le pointeur de pile est simplement déplacé vers le haut sans effacer les données en dessous, mais si vous commencez à l'attribution de nouvelles données sur la pile, le pointeur x peut en fait montrer que de nouvelles données à la place.
Préoccupations Spécifiques au Langage C++
Tout d'abord, il n'y a pas de soi-disant "pile" ou "tas" de l'allocation mandaté par le C++. Si vous parlez automatique d'objets dans le bloc de portée, ils sont même pas "affecté". (BTW, automatique de la durée de stockage dans C est certainement PAS le même pour "attribué"; le second est "dynamique" dans le C++ le langage.) Et la mémoire allouée dynamiquement est sur le free store, pas nécessairement sur "le tas", même si ce dernier est souvent l' (par défaut) mise en œuvre.
Bien que, comme par le résumé des règles sémantiques, automatique objets occupent encore de la mémoire, une C++ conforme de la mise en œuvre est autorisé à ignorer ce fait, quand il peut le prouver, ce qui n'a pas d'importance (quand il ne change pas les comportements observables du programme). Cette autorisation est accordée par le comme-si la règle en ISO C++, qui est aussi le grand clause permettant à l'habitude des optimisations (et il y a aussi presque la même règle en ISO C). Outre le cas de la règle, la norme ISO C++ a également copier élision des règles pour autoriser l'omission de certaines créations d'objets. Le constructeur et le destructeur des appels concernés sont donc omis. En conséquence, la fonction des objets (le cas échéant) dans ces constructeurs et destructeurs sont également éliminées, par rapport aux naïfs abstrait sémantique implicite par le code source.
D'autre part, free store allocation est certainement "allocation" de par leur conception. En vertu de la norme ISO C++ règles, une telle répartition peut être obtenue par un appel d'un fonction d'allocation. Cependant, depuis l'ISO C++14, il y a une nouvelle (non-comme-si) règle pour autoriser la fusion de répartition mondiale de fonction (c'est à dire
::operator new
) appels dans des cas spécifiques. Ce sont des parties de l'allocation dynamique opérations peuvent également être non-op comme le cas de l'automatique des objets.Des fonctions d'Allocation de répartir les ressources de la mémoire. Les objets peuvent encore être attribués en fonction de répartition à l'aide d'allocateurs. Automatique des objets, ils sont directement présentés, bien que le sous-jacent de la mémoire peut être consulté et être utilisé pour fournir de la mémoire à d'autres objets (par placement
new
), mais cela n'a pas grand sens comme le free store, car il n'y a aucun moyen de déplacer les ressources ailleurs.Toutes les autres préoccupations sont hors de la portée de C++. Néanmoins, ils peuvent encore être importante.
Sur les Implémentations de C++
C++ ne pas exposer réifiée de l'activation des enregistrements ou des sortes de première classe de suites (par exemple, par le célèbre
call/cc
), il n'y a aucun moyen de manipuler directement l'activation de l'enregistrement d'images où la mise en œuvre de placer le automatique des objets. Une fois n'est pas (non portable) interoperations avec l'implémentation sous-jacente ("native" du code non portable, tels que le code d'assembly en ligne), d'une omission de la sous-répartition des cadres peut être tout à fait banale. Par exemple, lorsque la fonction appelée est insérée, les images peuvent être efficacement fusionné dans d'autres, il n'existe aucun moyen de montrer ce qu'est la "répartition".Cependant, une fois que interops sont respectés, les choses sont plus complexes. Typique de mise en œuvre de C++, va exposer à la capacité de l'interopérabilité sur ISA (instruction set architecture), avec quelques conventions d'appel binaire frontière partagée avec les autochtones (ISA-machine) du code. Ce serait explicitement coûteux, notamment, lors de l'entretien de la pointeur de pile, qui est souvent directement détenus par une administration chargée de niveau de registre (avec probablement spécifique à la machine des instructions d'accès). Le pointeur de pile indique la limite de la partie supérieure du cadre de l' (en cours) d'appel de fonction. Lorsqu'un appel de fonction est entré, une nouvelle image est nécessaire, et le pointeur de pile est ajoutée ou soustraite (selon la convention de l'ISA) par une valeur non moins que la taille de l'image. L'image est alors dit alloué lorsque le pointeur de pile, après les opérations. Les paramètres des fonctions peuvent être transférés sur le frame de pile ainsi, selon la convention d'appel utilisé pour l'appel. Le cadre peut contenir la mémoire automatique des objets (dont probablement les paramètres spécifiés par le code source C++. Dans le sens de la mise en œuvre, ces objets sont "attribués". Lorsque la commande quitte l'appel de la fonction, l'image n'est plus nécessaire, il est généralement libérés par la restauration du pointeur de pile de revenir à l'état avant l'appel (préalablement enregistrée conformément à la convention d'appel). Ceci peut être considéré comme "libération". Ces opérations fait l'historique d'activation effectivement un PRINCIPE de structure de données, de sorte qu'il est souvent appelé "l' (appel) de la pile". Le pointeur de pile efficacement indique la position du haut de la pile.
Parce que la plupart C++ implémentations (en particulier ceux de ciblage de l'ISA au niveau du code natif et à l'aide de la langue de l'assembly que sa sortie immédiate) utilise des stratégies similaires comme cela, par exemple la confusion "allocation" régime est populaire. Ces allocations (ainsi que deallocations) faire passer les cycles machine, et il peut être coûteux lors de la (non optimisé) les appels se produisent fréquemment, même si moderne CPU microarchitectures peut avoir des optimisations mises en œuvre par le matériel pour la commune de modèle de code (comme l'utilisation d'un de la pile du moteur dans la mise en œuvre
PUSH
/POP
instructions).Mais de toute façon, en général, il est vrai que le coût de la trame de pile de répartition est nettement moins qu'un appel à une fonction d'attribution de l'exploitation du magasin gratuit (sauf s'il est totalement optimisé loin), qui lui-même peut avoir des centaines d' (si ce n'est des millions d' 🙂 les opérations de maintenir le pointeur de pile et d'autres états. Fonctions d'Allocation sont généralement basés sur l'API fournie par l'environnement hébergé (par exemple d'exécution fournis par le système d'exploitation). Différentes pour le but de la détention automatique des objets pour les appels de fonctions, ces affectations sont générale-dessein, de sorte qu'ils n'auront pas la structure du cadre, comme une pile. Traditionnellement, ils allouer de l'espace de la piscine de stockage appelé tas (ou plusieurs tas). Différente de la "pile", le concept de "tas", ici, n'indique pas la structure de données utilisée; il est dérivé du début des implémentations de langue il y a des décennies. (BTW, la pile d'appel est généralement attribuée à revenu fixe ou spécifié par l'utilisateur, la taille du segment de mémoire de l'environnement dans le programme ou le fil de démarrage.) La nature de l'utilisation des cas, les allocations et les deallocations à partir d'un tas beaucoup plus compliqué (que de la pousser ou de la pop de la pile d'images), et ne peut pas être directement optimisée par le matériel.
Effets sur l'Accès à la Mémoire
L'habitude de la pile de répartition de toujours mettre la nouvelle image sur le dessus, de sorte qu'il a une assez bonne localité. C'est sympathique de cache. Otoh, que, la mémoire allouée au hasard dans le magasin libre n'a pas cette propriété. Depuis ISO C++17, il y a des ressources du pool de modèles fournis par les
<memory>
. Le but immédiat de cette interface est de permettre à des résultats consécutifs à des allocations à proximité de la mémoire. Cette reconnaît le fait que cette stratégie est généralement bon pour les performances contemporaines mises en œuvre, par exemple, être aimable à cache dans les architectures modernes. C'est à propos de la performance de accès plutôt que allocation, si.Simultanéité
Attente de l'accès simultané de la mémoire peuvent avoir des effets différents entre la pile et le tas. Une pile d'appel est généralement exclusivement détenue par un seul thread d'exécution dans une implémentation C++. Otoh, que, tas sont souvent partagé entre les threads d'un processus. Pour ces tas, l'allocation et la désallocation de fonctions pour protéger l'interne commun de données administratives de la structure de données de la course. En conséquence, les allocations de tas et deallocations peut avoir d'autres frais généraux en raison de la synchronisation interne des opérations.
L'Efficacité De L'Espace
En raison de la nature du cas d'utilisation et des structures de données internes, des tas souffre de interne la fragmentation de la mémoire, tandis que la pile ne fonctionne pas. Cela n'a pas d'impacts directs sur la performance de l'allocation de mémoire, mais dans un système avec la mémoire virtuelle, peu d'espace, l'efficacité peut dégénérer les performances globales d'accès à la mémoire. C'est particulièrement horrible lorsque le disque dur est utilisé en tant que swap de la mémoire physique. Il peut causer de très long temps de latence parfois des milliards de cycles.
Limites de la Pile Allocations
Bien que la pile allocations sont souvent supérieurs à la performance que les allocations de tas dans la réalité, il ne veut certainement pas dire pile allocations pouvez toujours remplacer les allocations de tas.
Tout d'abord, il n'existe aucun moyen pour allouer de l'espace sur la pile avec une taille spécifiée au moment de l'exécution de façon portable avec ISO C++. Il y a des extensions fournies par les implémentations comme
alloca
et G++'s VLA (de longueur variable tableau), mais il y a des raisons à éviter de les utiliser. (IIRC, source de Linux supprime l'utilisation de VLA récemment.) (À noter également la norme ISO C99 n'ont VLA, mais l'ISO C11 tourne le soutien facultatif.)Deuxième, il n'y a pas de portable et fiable afin de détecter pile épuisement de l'espace. Ceci est souvent appelé débordement de pile (hmm, l'étymologie de ce site), mais probablement plus accruately, "débordements de pile". En réalité, ce qui provoque souvent des accès non valide de la mémoire et de l'état du programme est alors corruptied (...ou peut-être pire, un trou de sécurité). En fait, l'ISO C++ n'a pas de notion de pile et en fait un comportement indéfini lorsque la ressource est épuisée. D'être prudent sur la façon dont beaucoup de place devrait être à gauche pour le réglage automatique des objets.
Si l'espace de pile épuisée, il y a trop d'objet alloué dans la pile, qui peut être causée par de trop nombreux appels de fonctions ou de la mauvaise utilisation de l'objet. De tels cas peuvent suggérer l'existence de bugs, par exemple, une fonction récursive appel sans corriger les conditions de sortie.
Néanmoins, profonde appels récursifs sont parfois souhaitée. Dans les implémentations de langues qui ont besoin de soutien de unbound les appels actifs (profondeur d'appel uniquement limité par la mémoire totale), il est impossible pour utiliser l'appel des indigènes de la pile directement en tant que la langue cible activation de l'enregistrement comme typique de C++ implémentations. Par exemple, SML/NJ explicitement alloue images sur le tas et utilise cactus piles. La complexité de la répartition de ces activation de l'enregistrement d'images n'est généralement pas rapide comme la pile d'appel des cadres. Cependant, lors de la poursuite de la mise en œuvre de langues avec la bonne queue de récursivité, direct allocation de pile dans la langue d'objet (qui est, "l'objet" dans la langue ne pas stocker les références, mais des valeurs primitives qui peut être un-à-un localisé sur le partage des objets en C++) est encore plus compliqué avec plus de performances en général. Lors de l'utilisation de C++ en œuvre de ces langues, il est difficile d'estimer l'impact sur les performances.
heap
fréquemment.Ne jamais faire prématuré hypothèse que d'autres le code de l'application et de l'utilisation de l'impact de votre fonction. Donc en regardant la fonction est l'isolement est d'aucune utilité.
Si vous êtes sérieux avec application puis VTune ou utiliser de toute autre outil de profilage et de regarder les points chauds.
Ketan
Je voudrais dire, en fait le code générer par GCC (je me souviens VS aussi) n'ont pas les frais généraux de faire de l'allocation de pile.
Dire pour la fonction suivante:
Voici le code générer:
Donc whatevery combien de variable locale, vous avez (même à l'intérieur si ou commutateur), juste le 3880 va changer pour une autre valeur. Sauf si vous n'avez pas de variable locale, cette instruction juste besoin d'exécuter. Donc allouer une variable n'ont pas les frais généraux.