Après avoir parcouru plusieurs réponses à un dépassement de pile, il est clair que certains langages compilés de manière native ont un garbage collection . Mais je ne vois pas comment cela fonctionnerait.
Je comprends comment la récupération de place pourrait fonctionner avec un langage interprété. Le ramasse-miettes s'exécute simplement à côté de l'interprète et supprime les objets inutilisés et inaccessibles de la mémoire du programme. Ils courent tous les deux ensemble.
Comment cela fonctionnerait-il avec les langages compilés? D'après ce que je comprends, une fois que le compilateur a compilé le code source en code cible - en particulier le code machine natif - c'est fait. Son travail est fini. Alors, comment le programme compilé pourrait-il également être récupéré?
Le compilateur fonctionne-t-il avec le processeur d'une manière ou d'une autre pendant l'exécution du programme pour supprimer les objets "garbage"? Ou le compilateur inclut-il un collecteur de mémoire minimal dans l'exécutable du programme compilé?
Je crois que ma dernière déclaration aurait plus de validité que la première en raison de cet extrait de cette réponse à Stack Overflow :
Un de ces langages de programmation est Eiffel. La plupart des compilateurs Eiffel génèrent du code C pour des raisons de portabilité. Ce code C est utilisé pour produire du code machine par un compilateur C standard. Les implémentations Eiffel fournissent GC (et parfois même des CG précis) pour ce code compilé, et aucun ordinateur virtuel n'est nécessaire. En particulier, le compilateur VisualEiffel a généré le code machine x86 natif directement avec le support complet du GC .
La dernière déclaration semble impliquer que le compilateur inclut un programme dans l'exécutable final qui agit comme un ramasse-miettes pendant l'exécution du programme.
La page du site Web du langage D sur la récupération de place, compilée de manière native et dotée d'un récupérateur de place facultatif, semble également indiquer qu'un programme d'arrière-plan s'exécute parallèlement au programme exécutable d'origine pour implémenter la récupération de place.
D est un langage de programmation système prenant en charge la récupération de place. Habituellement, il n'est pas nécessaire de libérer explicitement la mémoire. Allouez-le au besoin, et le ramasse-miettes retournera périodiquement toute la mémoire inutilisée dans le pool de mémoire disponible.
Si la méthode mentionnée ci-dessus est utilisée, comment cela fonctionnerait-il? Le compilateur stocke-t-il une copie d'un programme de récupération de place et la colle-t-elle dans chaque exécutable généré?
Ou suis-je imparfait dans ma pensée? Si tel est le cas, quelles méthodes sont utilisées pour implémenter la récupération de place pour les langages compilés et comment fonctionnent-elles exactement?
la source
malloc()
.Réponses:
La récupération de place dans un langage compilé fonctionne de la même manière que dans un langage interprété. Des langues telles que Go utilisent des récupérateurs de mémoire, même si leur code est généralement compilé à l'avance pour le code machine.
(Suivi) La récupération de place commence généralement par parcourir les piles d'appels de tous les threads en cours d'exécution. Les objets sur ces piles sont toujours vivants. Ensuite, le ramasse-miettes parcourt tous les objets pointés par des objets vivants jusqu'à ce que tout le graphe d'objet actif soit découvert.
Il est clair que cela nécessite des informations supplémentaires que les langages tels que C ne fournissent pas. En particulier, il nécessite une carte du cadre de pile de chaque fonction contenant les décalages de tous les pointeurs (et probablement de leurs types de données), ainsi que des cartes de toutes les présentations d'objet contenant les mêmes informations.
Il est cependant facile de voir que les langues qui ont des garanties de type fortes (par exemple, si les conversions de pointeur vers différents types de données sont interdites) peuvent effectivement calculer ces cartes au moment de la compilation. Ils stockent simplement une association entre les adresses d'instruction et les cartes de trame de pile et une association entre les types de données et les cartes de présentation d'objet à l'intérieur du fichier binaire. Cette information leur permet ensuite de faire la traversée du graphe d'objet.
Le ramasse-miettes lui-même n'est rien de plus qu'une bibliothèque liée au programme, similaire à la bibliothèque standard C. Par exemple, cette bibliothèque pourrait fournir une fonction similaire à
malloc()
celle qui exécute l'algorithme de collecte si la pression de la mémoire est élevée.la source
Cela semble peu élégant et bizarre, mais oui. Le compilateur a une bibliothèque d’utilitaires complète, contenant beaucoup plus que du code de récupération de place, et les appels à cette bibliothèque seront insérés dans chaque exécutable qu’il crée. C'est ce qu'on appelle la bibliothèque d'exécution , et vous seriez surpris du nombre de tâches différentes qu'elle sert habituellement.
la source
malloc()
etfree()
n'est pas intégré à la langue, ne fait pas partie du système d'exploitation, mais fait partie de cette bibliothèque. C ++ est aussi parfois compilé avec une bibliothèque de récupération de place, même si le langage n'a pas été conçu avec GC.dynamic_cast
et que les exceptions fonctionnent, même si vous n'ajoutez pas de GC.main()
, et il est parfaitement légal de lancer un thread GC dans ce code. (En supposant que le GC ne soit pas implémenté dans les appels d'allocation de mémoire.) Au moment de l'exécution, le GC n'a vraiment besoin que de savoir quelles parties d'un objet sont des pointeurs ou des références d'objet, et le compilateur doit émettre le code pour traduire une référence d'objet en pointeur. si le CPG déplace des objets.crt0.o
( ce qui signifie « C R un T ime, les bases »), qui obtient lié à tous les programmes (ou au moins tous les programmes qui ne sont pas autoportants ).C'est une façon étrange de dire «le compilateur lie le programme à une bibliothèque qui effectue le ramassage des ordures». Mais oui, c'est ce qui se passe.
Cela n'a rien de spécial: les compilateurs lient généralement des tonnes de bibliothèques aux programmes qu'ils compilent; autrement, les programmes compilés ne pourraient pas faire grand chose sans ré-implémenter beaucoup de choses: même écrire du texte à l'écran / un fichier /… nécessite une bibliothèque.
Mais peut-être que GC est différent de ces autres bibliothèques, qui fournissent des API explicites que l'utilisateur appelle?
Non: dans la plupart des langues, les bibliothèques d'exécution effectuent beaucoup de tâches en arrière-plan sans API publique, au-delà de GC. Considérez ces trois exemples:
Ainsi, une bibliothèque de récupération de place n'a rien de spécial, et a priori n'a rien à voir avec le fait qu'un programme a été compilé à l'avance.
la source
Votre formulation est fausse. Un langage de programmation est une spécification écrite dans un rapport technique (pour un bon exemple, voir R5RS ). En fait, vous faites référence à une implémentation de langage spécifique (qui est un logiciel).
(certains langages de programmation ont de mauvaises spécifications, voire des références manquantes, ou se conforment simplement à un exemple d'implémentation; néanmoins, un langage de programmation définit un comportement - par exemple, il a une syntaxe et une sémantique -, ce n'est pas un logiciel, mais pourrait l'être implémenté par certains logiciels; de nombreux langages de programmation ont plusieurs implémentations, en particulier, "compilé" est un adjectif qui s'applique aux implémentations - même si certains langages de programmation sont plus faciles à implémenter par des interprètes que par des compilateurs.)
Notez que les interprètes et les compilateurs ont une signification vague et que certaines implémentations de langage peuvent être considérées comme étant les deux. En d'autres termes, il existe un continuum entre les deux. Lisez le dernier livre de dragon et réfléchissez au bytecode , à la compilation JIT , à l’ émission dynamique de code C compilé dans un "plugin" puis à dlopen (3), soumis au même processus (et sur les machines actuelles, cela est suffisamment rapide pour être compatible avec un REPL interactif , voir ceci )
Je recommande fortement de lire le manuel du GC . Un livre entier est nécessaire pour répondre . Avant cela, lisez le wikipage Garbage Collection (que je suppose que vous avez lu avant de le lire ci-dessous).
Le système d'exécution de l'implémentation du langage compilé contient le ramasse-miettes et le compilateur génère un code adapté à ce système d'exécution particulier. En particulier, les primitives d’allocation (compilées en code machine) appellent (ou peuvent) appeler le système d’exécution.
Juste en émettant un code machine qui utilise (et est "convivial" et "compatible avec") le système d’exécution.
Notez que vous pouvez trouver plusieurs bibliothèques de collecte des ordures, en particulier Boehm GC , MPS Ravenbrook , ou même mon (unmaintained) Qish . Et coder un GC simple n’est pas très difficile (cependant, le déboguer est plus difficile et coder un GC concurrentiel est difficile ).
Dans certains cas, le compilateur utiliserait un GC conservateur (comme Boehm GC ). Ensuite, il n'y a pas grand chose à coder. Le CPG conservateur (lorsque le compilateur appelle sa routine d’allocation ou la routine entière du GC) analyse parfois la pile d’appels entière et suppose que toute zone mémoire (indirectement) accessible depuis la pile d’appels est active. Ceci est appelé un GC conservateur car les informations de frappe sont perdues: si un entier de la pile d'appels ressemble à une adresse, il sera suivi, etc.
Dans d'autres cas (plus difficiles), le moteur d'exécution fournit un garbage collection pour la copie générationnelle (un exemple typique est le compilateur Ocaml, qui compile le code Ocaml en code machine à l'aide d'un tel GC). Ensuite, le problème est de trouver précisément sur la pile d’appels tous les pointeurs, et certains d’entre eux sont déplacés par le GC. Ensuite, le compilateur génère des méta-données décrivant les cadres de pile d'appels, que le moteur d'exécution utilise. Ainsi, les conventions d’appel et ABI deviennent spécifiques à cette implémentation (c’est-à-dire un compilateur) et à ce système d’exécution.
Dans certains cas, le code machine généré par le compilateur (même les fermetures pointant vers lui) est lui-même récupéré . C'est notamment le cas de SBCL (une bonne implémentation de Common Lisp) qui génère un code machine pour chaque interaction REPL . Cela nécessite également des méta-données décrivant le code et les cadres d'appel utilisés à l'intérieur.
Sorte de. Cependant, le système d'exécution peut être une bibliothèque partagée, etc. Parfois (sous Linux et plusieurs autres systèmes POSIX), il peut même s'agir d'un interpréteur de script, par exemple transmis à execve (2) avec un shebang . Ou un interprète ELF , voir elf (5) et
PT_INTERP
, etc.BTW, la plupart des compilateurs pour la langue avec garbage collection (et leur système d’exécution) sont aujourd’hui des logiciels libres . Alors téléchargez le code source et étudiez-le.
la source
Array#[]
pire casHash#[]
0 (1), le pire cas amorti 0 (1)). Et le dernier mais non le moindre: le cerveau de Matz.Il y a déjà de bonnes réponses, mais j'aimerais dissiper certains malentendus derrière cette question.
Il n'y a pas de "langage nativement compilé" en soi. Par exemple, le même code Java a été interprété (puis partiellement compilé au moment de l'exécution) sur mon ancien téléphone (Java Dalvik) et est compilé (à l'avance) sur mon nouveau téléphone (ART).
La différence entre exécuter du code en mode natif et interprété est beaucoup moins stricte qu'il n'y parait. Les deux ont besoin de bibliothèques d'exécution et d'un système d'exploitation pour fonctionner (*). Le code interprété a besoin d'un interprète, mais l'interprète n'est qu'une partie du moteur d'exécution. Mais même cela n’est pas strict, car vous pourriez remplacer l’interprète par un compilateur (juste à temps). Pour obtenir des performances optimales, vous pouvez choisir les deux (le runtime Java sur le bureau contient un interpréteur et deux compilateurs).
Peu importe comment exécuter le code, il devrait se comporter de la même manière. L'affectation et la libération de mémoire sont des tâches qui incombent au moteur d'exécution (tout comme l'ouverture de fichiers, le démarrage de threads, etc.). Dans votre langue, vous écrivez
new X()
ou vous ressemblez. La spécification de langue indique ce qui doit arriver et le moteur d'exécution le fait.Une partie de la mémoire disponible est allouée, le constructeur est appelé, etc. S'il n'y a pas assez de mémoire, le garbage collector est appelé. Comme vous êtes déjà dans le runtime, qui est un morceau de code natif, l'existence d'un interprète n'a pas d'importance.
Il n'y a vraiment pas de lien direct entre l'interprétation du code et la récupération de place. C'est juste que les langages de bas niveau tels que C sont conçus pour la vitesse et le contrôle fin de tout, ce qui ne cadre pas bien avec l'idée d'un code non natif ou d'un ramasse-miettes. Donc, il n'y a qu'une corrélation.
C'était très vrai dans les temps anciens, où par exemple, l'interpréteur Java était très lent et le ramasse-miettes plutôt inefficace. De nos jours, les choses sont très différentes et parler d'un langage interprété a perdu tout sens.
(*) Au moins quand on parle de code à usage général, en laissant de côté les chargeurs de démarrage et autres.
la source
java myprog
est autant ou aussi peu natif quegrep myname /etc/passwd
orld.so myprog
: c'est un exécutable (peu importe ce que cela veut dire) qui prend un argument et effectue des opérations avec les données.Les détails varient selon les implémentations, mais il s’agit généralement d’une combinaison des éléments suivants:
Dans les GC incrémentiels et simultanés, le code compilé et le GC doivent coopérer pour gérer certains invariants. Par exemple, dans un collecteur de copie, le CPG copie les données en temps réel de l’espace A à l’espace B, en laissant derrière elles les déchets. Pour le cycle suivant, il retourne A et B et se répète. Une règle peut donc être de s'assurer que chaque fois que le programme utilisateur tente de se référer à un objet dans l'espace A, cela est détecté et l'objet est immédiatement copié dans l'espace B, où le programme peut continuer à y accéder. Une adresse de transfert est laissée dans l'espace A pour indiquer au GC que cela s'est produit, de sorte que toute autre référence à l'objet soit mise à jour au fur et à mesure de son traçage. Ceci est connu comme une "barrière de lecture".
Les algorithmes GC ont été étudiés depuis les années 60 et il existe une littérature abondante sur le sujet. Google si vous voulez plus d'informations.
la source