Il me semble que tout ce qui peut être fait avec une pile peut être fait avec le tas, mais tout ce qui peut être fait avec le tas ne peut pas être fait avec la pile. Est-ce exact? Alors pour des raisons de simplicité, et même si nous perdons un peu de performances avec certaines charges de travail, ne pourrait-il pas être préférable de ne choisir qu'une seule norme (c'est-à-dire le tas)?
Pensez au compromis entre modularité et performances. Je sais que ce n'est pas la meilleure façon de décrire ce scénario, mais en général, il semble que la simplicité de compréhension et de conception pourrait être une meilleure option même s'il existe un potentiel pour de meilleures performances.
Réponses:
Les tas sont mauvais pour l'allocation et la désallocation rapides de la mémoire. Si vous souhaitez récupérer de très petites quantités de mémoire pour une durée limitée, un tas n'est pas votre meilleur choix. Une pile, avec son algorithme d'allocation / désallocation super simple, excelle naturellement dans ce domaine (encore plus si elle est intégrée au matériel), c'est pourquoi les gens l'utilisent pour des choses comme passer des arguments aux fonctions et stocker des variables locales - le plus L'inconvénient important est qu'il a un espace limité, et donc garder de gros objets dedans, ou essayer de l'utiliser pour des objets à longue durée de vie, sont deux mauvaises idées.
Se débarrasser complètement de la pile dans le but de simplifier un langage de programmation est la mauvaise façon OMI - une meilleure approche serait de résumer les différences, laissez le compilateur déterminer le type de stockage à utiliser, tandis que le programmeur met en place plus haut- des constructions de niveau plus proches de la façon dont les humains pensent - et en fait, des langages de haut niveau comme C #, Java, Python etc. font exactement cela. Ils offrent une syntaxe presque identique pour les objets alloués en tas et les primitives allouées par pile («types de référence» vs «types de valeur» dans le jargon .NET), soit entièrement transparents, soit avec quelques différences fonctionnelles que vous devez comprendre pour utiliser le langage correctement (mais vous n'avez pas réellement besoin de savoir comment une pile et un tas fonctionnent en interne).
la source
Autrement dit, une pile n'est pas un peu de performance. C'est des centaines ou des milliers de fois plus rapide que le tas. De plus, la plupart des machines modernes prennent en charge le matériel pour la pile (comme x86) et cette fonctionnalité matérielle, par exemple pour la pile d'appels, ne peut pas être supprimée.
la source
Non
La zone de pile en C ++ est incroyablement rapide en comparaison. Je suppose qu'aucun développeur C ++ expérimenté ne serait disposé à désactiver cette fonctionnalité.
Avec C ++, vous avez le choix et vous avez le contrôle. Les concepteurs n'étaient pas particulièrement enclins à introduire des fonctionnalités qui ajoutaient un temps ou un espace d'exécution important.
Exercer ce choix
Si vous souhaitez créer une bibliothèque ou un programme qui nécessite que chaque objet soit alloué dynamiquement, vous pouvez le faire avec C ++. Il s'exécuterait relativement lentement, mais vous pourriez alors avoir cette «modularité». Pour le reste d'entre nous, la modularité est toujours facultative, introduisez-la au besoin car les deux sont nécessaires pour des implémentations bonnes / rapides.
Alternatives
Il existe d'autres langages qui nécessitent que le stockage de chaque objet soit créé sur le tas; il est assez lent, de sorte qu'il compromet les conceptions (programmes du monde réel) d'une manière pire que d'avoir à apprendre les deux (OMI).
Les deux sont importants, et C ++ vous donne la puissance à utiliser efficacement pour chaque scénario donné. Cela dit, le langage C ++ peut ne pas être idéal pour votre conception, si ces facteurs dans votre OP sont importants pour vous (par exemple, lisez sur les langages de niveau supérieur).
la source
En fait, la performance est probablement considérable!
Comme d'autres l'ont souligné, les piles sont une structure extrêmement efficace pour gérer les données qui obéissent aux règles LIFO (dernier entré premier sorti). L'allocation / libération de mémoire sur la pile est généralement juste une modification d'un registre sur le CPU. La modification d'un registre est presque toujours l'une des opérations les plus rapides qu'un processeur puisse effectuer.
Le tas est généralement une structure de données assez complexe et l'allocation / libération de mémoire prendra de nombreuses instructions pour effectuer toute la comptabilité associée. Pire encore, dans les implémentations courantes, chaque appel à travailler avec le tas peut entraîner un appel au système d'exploitation. Les appels du système d'exploitation prennent beaucoup de temps! Le programme doit généralement passer du mode utilisateur au mode noyau, et chaque fois que cela se produit, le système d'exploitation peut décider que d'autres programmes ont des besoins plus urgents et que votre programme devra attendre.
la source
Simula a utilisé le tas pour tout. Tout mettre sur le tas induit toujours un niveau d'indirection supplémentaire pour les variables locales, et cela met une pression supplémentaire sur le garbage collector (vous devez tenir compte du fait que les garbage collector étaient vraiment aspirés à l'époque). C'est en partie pourquoi Bjarne a inventé le C ++.
la source
Les piles sont extrêmement efficaces pour les données LIFO, telles que les métadonnées associées aux appels de fonction, par exemple. La pile exploite également les caractéristiques de conception inhérentes au processeur. Étant donné que les performances à ce niveau sont fondamentales pour à peu près tout le reste dans un processus, prendre ce "petit" coup à ce niveau se propagera très largement. De plus, la mémoire de tas est déplaçable par le système d'exploitation, ce qui serait mortel pour les piles. Bien qu'une pile puisse être implémentée dans le tas, elle nécessite des frais généraux qui affecteront littéralement chaque élément d'un processus au niveau le plus granulaire.
la source
«efficace» en termes d'écriture de code peut-être, mais certainement pas en termes d'efficacité de votre logiciel. Les allocations de pile sont essentiellement gratuites (il suffit de quelques instructions machine pour déplacer le pointeur de pile et réserver de l'espace sur la pile pour les variables locales).
Étant donné que l'allocation de pile ne prend presque pas de temps, une allocation, même sur un tas très efficace, sera 100k (sinon 1M +) de fois plus lente.
Imaginez maintenant combien de variables locales et d'autres structures de données une application typique utilise. Chaque petit «i» que vous utilisez comme compteur de boucle est alloué un million de fois plus lentement.
Bien sûr, si le matériel est assez rapide, vous pouvez écrire une application qui utilise uniquement le tas. Mais imaginez maintenant quel type d'application vous pourriez écrire si vous profitiez du tas et utilisiez le même matériel.
la source
Vous pourriez avoir un intérêt pour "La collecte des ordures est rapide, mais une pile est plus rapide".
http://dspace.mit.edu/bitstream/handle/1721.1/6622/AIM-1462.ps.Z
Si je l'ai lu correctement, ces gars-là ont modifié un compilateur C pour allouer des "cadres de pile" sur le tas, puis utilisent la récupération de place pour désallouer les cadres au lieu de faire sauter la pile.
Les "trames de pile" allouées par pile surpassent de manière décisive les "trames de pile" allouées par tas.
la source
Comment la pile d'appels va-t-elle fonctionner sur un tas? Essentiellement, vous devrez allouer une pile sur le tas dans chaque programme, alors pourquoi ne pas faire le matériel OS + pour vous?
Si vous voulez que les choses soient vraiment simples et efficaces, donnez simplement à l'utilisateur sa partie de mémoire et laissez-le s'en occuper. Bien sûr, personne ne veut tout implémenter et c'est pourquoi nous avons une pile et un tas.
la source
La pile et le tas sont requis. Ils sont utilisés dans différentes situations, par exemple:
Fondamentalement, les mécanismes ne peuvent pas être comparés du tout car tant de détails sont différents. La seule chose commune avec eux est qu'ils gèrent tous les deux la mémoire d'une manière ou d'une autre.
la source
Les ordinateurs modernes disposent de plusieurs couches de mémoire cache en plus d'un système de mémoire principal volumineux mais lent. On peut faire des dizaines d'accès à la mémoire cache la plus rapide dans le temps requis pour lire ou écrire un octet à partir du système de mémoire principal. Ainsi, accéder à un emplacement mille fois est beaucoup plus rapide que d'accéder à 1000 (ou même 100) emplacements indépendants une fois chacun. Étant donné que la plupart des applications allouent et désallouent de façon répétée de petites quantités de mémoire près du haut de la pile, les emplacements en haut de la pile sont utilisés et réutilisés énormément, de sorte que la grande majorité (99% + dans une application typique) des accès de pile peuvent être gérés à l'aide de la mémoire cache.
En revanche, si une application devait créer et abandonner à plusieurs reprises des objets de segment de mémoire pour stocker des informations de continuation, chaque version de chaque objet de pile jamais créé devrait être écrite dans la mémoire principale. Même si la grande majorité de ces objets seraient complètement inutiles au moment où le processeur voudrait recycler les pages de cache dans lesquelles ils ont commencé, le processeur n'aurait aucun moyen de le savoir. Par conséquent, le processeur devrait perdre beaucoup de temps à effectuer des écritures de mémoire lentes d'informations inutiles. Pas exactement une recette pour la vitesse.
Une autre chose à considérer est que, dans de nombreux cas, il est utile de savoir qu'une référence d'objet passée à une routine ne sera pas utilisée une fois la routine terminée. Si les paramètres et les variables locales sont passés via la pile, et si l'inspection du code de la routine révèle qu'il ne persiste pas une copie de la référence transmise, alors le code qui appelle la routine peut être sûr que si aucune référence externe à la l'objet existait avant l'appel, aucun n'existera après. En revanche, si des paramètres étaient passés via des objets de tas, des concepts comme "après le retour d'une routine" deviennent un peu plus nébuleux, car si le code conservait une copie de la suite, il serait possible pour la routine de "retourner" plus d'une fois après un appel unique.
la source