Nach dem Durchsuchen mehrerer Antworten eines Stapelüberlaufs ist klar, dass einige nativ kompilierte Sprachen eine Garbage Collection haben . Aber mir ist unklar, wie genau das funktionieren würde.
Ich verstehe, wie Garbage Collection mit einer interpretierten Sprache funktionieren kann. Der Garbage Collector wird einfach neben dem Interpreter ausgeführt und löscht nicht verwendete und nicht erreichbare Objekte aus dem Speicher des Programms. Sie laufen beide zusammen.
Wie würde das mit kompilierten Sprachen funktionieren? Ich verstehe, dass, sobald der Compiler den Quellcode in den Zielcode kompiliert hat - insbesondere nativen Maschinencode -, es getan ist. Seine Arbeit ist beendet. Also, wie könnte das kompilierte Programm auch als Müll eingesammelt werden?
Arbeitet der Compiler in irgendeiner Weise mit der CPU zusammen, während das Programm zum Löschen von "Garbage" -Objekten ausgeführt wird? Oder enthält der Compiler einen minimalen Garbage Collector in der ausführbaren Datei des kompilierten Programms.
Ich glaube, meine letztere Aussage hätte aufgrund dieses Auszugs aus dieser Antwort zum Stapelüberlauf mehr Gültigkeit als die erstere :
Eine solche Programmiersprache ist Eiffel. Die meisten Eiffel-Compiler generieren aus Gründen der Portabilität C-Code. Dieser C-Code wird verwendet, um Maschinencode von einem Standard-C-Compiler zu erzeugen. Eiffel-Implementierungen bieten GC (und manchmal sogar genaue GC) für diesen kompilierten Code, und es ist keine VM erforderlich. Insbesondere hat der VisualEiffel-Compiler nativen x86-Maschinencode direkt mit vollständiger GC-Unterstützung generiert .
Die letzte Aussage scheint zu implizieren, dass der Compiler ein Programm in die endgültige ausführbare Datei einschließt, das als Garbage Collector fungiert, während das Programm ausgeführt wird.
Die Seite auf der D-Language-Website über die Garbage Collection - die nativ kompiliert wurde und optional über einen Garbage Collector verfügt - scheint auch darauf hinzudeuten, dass einige Hintergrundprogramme neben dem ursprünglichen ausführbaren Programm zur Implementierung der Garbage Collection ausgeführt werden.
D ist eine Systemprogrammiersprache mit Unterstützung für die Garbage Collection. Normalerweise ist es nicht erforderlich, Speicher explizit freizugeben. Ordnen Sie es einfach nach Bedarf zu, und der Garbage Collector gibt in regelmäßigen Abständen den gesamten nicht verwendeten Speicher in den Pool des verfügbaren Speichers zurück.
Wenn die oben genannten Verfahren wird verwendet, wie genau würde es funktionieren? Speichert der Compiler eine Kopie eines Garbage Collection-Programms und fügt sie in jede von ihm generierte ausführbare Datei ein?
Oder habe ich einen Denkfehler? Wenn ja, welche Methoden werden zur Implementierung der Garbage Collection für kompilierte Sprachen verwendet und wie genau würden sie funktionieren?
quelle
malloc()
.Antworten:
Die Garbage Collection in einer kompilierten Sprache funktioniert genauso wie in einer interpretierten Sprache. Sprachen wie Go verwenden Tracing-Garbage-Collectors, obwohl ihr Code normalerweise im Voraus kompiliert wird, um Code zu verarbeiten.
Die (Ablaufverfolgungs-) Speicherbereinigung beginnt normalerweise mit dem Durchlaufen der Aufrufstapel aller derzeit ausgeführten Threads. Objekte auf diesen Stapeln sind immer aktiv. Danach durchläuft der Garbage Collector alle Objekte, auf die von Live-Objekten verwiesen wird, bis der gesamte Live-Objekt-Graph erkannt wurde.
Es ist klar, dass dies zusätzliche Informationen erfordert, die Sprachen wie C nicht bieten. Insbesondere ist eine Karte des Stapelrahmens jeder Funktion erforderlich, die die Offsets aller Zeiger (und wahrscheinlich ihrer Datentypen) sowie Karten aller Objektlayouts enthält, die dieselben Informationen enthalten.
Es ist jedoch leicht zu erkennen, dass Sprachen mit starken Typgarantien (z. B. wenn Zeigerverbindungen zu verschiedenen Datentypen nicht zulässig sind) diese Maps beim Kompilieren tatsächlich berechnen können. Sie speichern einfach eine Zuordnung zwischen Anweisungsadressen und Stack-Frame-Maps und eine Zuordnung zwischen Datentypen und Objektlayout-Maps innerhalb der Binärdatei. Diese Informationen ermöglichen es ihnen dann, die Objektgraphendurchquerung durchzuführen.
Der Garbage Collector selbst ist nichts anderes als eine mit dem Programm verknüpfte Bibliothek, ähnlich der C-Standardbibliothek. Diese Bibliothek könnte beispielsweise eine ähnliche Funktion bereitstellen
malloc()
, die den Erfassungsalgorithmus ausführt, wenn der Speicherdruck hoch ist.quelle
Es klingt unelegant und komisch, aber ja. Der Compiler verfügt über eine vollständige Dienstprogrammbibliothek, die viel mehr als nur Garbage Collection-Code enthält. Aufrufe dieser Bibliothek werden in jede von ihm erstellte ausführbare Datei eingefügt. Dies wird als Laufzeitbibliothek bezeichnet , und Sie werden überrascht sein, wie viele verschiedene Aufgaben normalerweise ausgeführt werden.
quelle
malloc()
undfree()
sind in der Sprache nicht gebaut, sind nicht Teil des Betriebssystems, sondern sind Funktionen in dieser Bibliothek. C ++ wird manchmal auch mit einer Garbage Collection-Bibliothek kompiliert, obwohl die Sprache nicht für GC entwickelt wurde.dynamic_cast
und Ausnahmen funktionieren, auch wenn Sie keinen GC hinzufügen.main()
, und es ist durchaus zulässig, beispielsweise einen GC-Thread in diesem Code zu starten. (Vorausgesetzt, der GC wird nicht innerhalb von Speicherzuweisungsaufrufen ausgeführt.) Zur Laufzeit muss der GC nur wirklich wissen, welche Teile eines Objekts Zeiger oder Objektreferenzen sind, und der Compiler muss den Code ausgeben, um eine Objektreferenz auf einen Zeiger zu übersetzen wenn der GC Objekte verschiebt.crt0.o
(der für " C R un T ime, das Wesentliche" steht), der mit jedem Programm (oder zumindest jedem Programm, das nicht frei verfügbar ist ) verknüpft wird .Das ist eine seltsame Art zu sagen: "Der Compiler verknüpft das Programm mit einer Bibliothek, die die Garbage Collection durchführt." Aber ja, genau das passiert.
Das ist nichts Besonderes: Compiler verknüpfen normalerweise Tonnen von Bibliotheken mit den Programmen, die sie kompilieren. Andernfalls könnten kompilierte Programme nicht viel tun, ohne viele Dinge von Grund auf neu zu implementieren: Selbst das Schreiben von Text auf den Bildschirm / eine Datei / ... erfordert eine Bibliothek.
Aber vielleicht unterscheidet sich GC von diesen anderen Bibliotheken, die explizite APIs bereitstellen, die der Benutzer aufruft?
Nein: In den meisten Sprachen arbeiten die Laufzeitbibliotheken viel hinter den Kulissen ohne öffentlich zugängliche API, über GC hinaus. Betrachten Sie diese drei Beispiele:
Eine Garbage Collection Library ist also nichts Besonderes, und a priori hat nichts damit zu tun, ob ein Programm im Voraus kompiliert wurde.
quelle
Deine Formulierung ist falsch. Eine Programmiersprache ist eine Spezifikation, die in einem technischen Bericht geschrieben wurde (ein gutes Beispiel finden Sie in R5RS ). Eigentlich meinen Sie sind bis zu einem gewissen spezifischen Sprache Implementierung (die eine Software ist).
(Einige Programmiersprachen haben schlechte oder sogar fehlende Spezifikationen oder entsprechen nur einer Beispielimplementierung. Dennoch definiert eine Programmiersprache ein Verhalten - z. B. hat sie eine Syntax und eine Semantik -, ist kein Softwareprodukt, könnte es aber sein implementiert von einem Softwareprodukt; viele Programmiersprachen haben mehrere Implementierungen; insbesondere ist "kompiliert" ein Adjektiv, das für Implementierungen gilt - auch wenn einige Programmiersprachen von Interpreten einfacher implementiert werden als von Compilern.)
Beachten Sie, dass Interpreter und Compiler eine lose Bedeutung haben und einige Sprachimplementierungen als beides angesehen werden können. Mit anderen Worten, dazwischen gibt es ein Kontinuum. Lesen Sie das neueste Dragon Book und denken Sie über Bytecode , JIT-Kompilierung und dynamisch emittierenden C-Code nach, der nach demselben Verfahren in ein "Plugin" kompiliert und dann mit dlopen (3) geöffnet wird (und auf aktuellen Computern ist dies schnell genug, um kompatibel zu sein) eine interaktive REPL , siehe dies )
Ich empfehle dringend, das GC-Handbuch zu lesen . Zur Beantwortung wird ein ganzes Buch benötigt . Lesen Sie vorher die Garbage Collection- Wikipage (von der ich annehme, dass Sie sie gelesen haben, bevor Sie sie weiter unten lesen).
Das Laufzeitsystem der kompilierten Sprache Implementierung enthält die Speicherbereinigungseinrichtung , und die Compiler Code zu erzeugen , das ist fit zu diesem bestimmten Laufzeitsystem. Insbesondere Zuordnungsprimitive (werden zu Maschinencode kompiliert), die das Laufzeitsystem aufrufen (oder möglicherweise aufrufen).
Nur durch die Ausgabe von Maschinencode, der das Laufzeitsystem verwendet (und "freundlich" und "kompatibel mit" ist).
Beachten Sie, dass Sie mehrere Garbage Collection-Bibliotheken finden, insbesondere Boehm GC , Ravenbrooks MPS oder sogar meinen (nicht verwalteten) Qish . Das Codieren eines einfachen GC ist nicht sehr schwierig (das Debuggen ist jedoch schwieriger und das Codieren eines kompetitiven GC ist schwierig ).
In einigen Fällen verwendete der Compiler einen konservativen GC (wie Boehm GC ). Dann gibt es nicht viel zu codieren. Der konservative GC scannt (wenn der Compiler seine Zuweisungsroutine oder die gesamte GC-Routine aufruft) manchmal den gesamten Aufrufstapel und geht davon aus, dass eine vom Aufrufstapel (indirekt) erreichbare Speicherzone aktiv ist. Dies wird als konservativer GC bezeichnet, da Tippinformationen verloren gehen: Wenn eine Ganzzahl auf dem Aufrufstapel wie eine Adresse aussieht, wird sie befolgt, usw.
In anderen (schwierigeren) Fällen bietet die Laufzeitumgebung eine Garbage Collection zum Kopieren von Daten (ein typisches Beispiel ist der Ocaml-Compiler, der Ocaml-Code mit einem solchen GC zu Maschinencode kompiliert). Dann geht es darum, auf den Aufrufstapeln genau alle Zeiger zu finden, von denen einige vom GC verschoben werden. Anschließend generiert der Compiler Metadaten, die Call-Stack-Frames beschreiben, die von der Laufzeit verwendet werden. So ist die Aufrufkonventionen und ABI immer spezifisch auf diese Implementierung (dh Compiler) und Laufzeitsystem.
In einigen Fällen handelt es sich bei dem vom Compiler generierten Maschinencode (tatsächlich sogar bei darauf verweisenden Abschlüssen ) um selbst gesammelten Müll . Dies gilt insbesondere für SBCL (eine gute Common-Lisp-Implementierung), die für jede REPL- Interaktion Maschinencode generiert . Dies erfordert auch einige Metadaten, die den Code und die darin verwendeten Aufrufrahmen beschreiben.
Art-of. Das Laufzeitsystem kann jedoch eine gemeinsam genutzte Bibliothek usw. sein. Manchmal (unter Linux und mehreren anderen POSIX-Systemen) kann es sich sogar um einen Skriptinterpreter handeln, der z. B. mit einem Shebang an execve (2) übergeben wird . Oder ein ELF- Dolmetscher, siehe elf (5) und usw.
PT_INTERP
Übrigens sind die meisten Compiler für Sprache mit Garbage Collection (und deren Laufzeitsystem) heute freie Software . Laden Sie den Quellcode herunter und studieren Sie ihn.
quelle
Array#[]
ist O (1) Worst-Case,Hash#[]
ist O (1) amortisiert Worst-Case). Und zu guter Letzt: Matzs Gehirn.Es gibt bereits einige gute Antworten, aber ich möchte einige Missverständnisse hinter dieser Frage klären.
Es gibt keine "nativ kompilierte Sprache" an sich. Beispielsweise wurde derselbe Java-Code auf meinem alten Telefon (Java Dalvik) interpretiert (und zur Laufzeit teilweise just-in-time kompiliert) und auf meinem neuen Telefon (ART) (im Voraus) kompiliert.
Der Unterschied zwischen der Ausführung von Code nativ und interpretiert ist viel weniger streng als es scheint. Beide benötigen einige Laufzeitbibliotheken und ein Betriebssystem, um zu funktionieren (*). Der interpretierte Code benötigt einen Interpreter, aber der Interpreter ist nur ein Teil der Laufzeit. Aber auch dies ist nicht streng, da Sie den Interpreter durch einen (Just-in-Time-) Compiler ersetzen könnten. Für eine maximale Leistung möchten Sie möglicherweise beides (die Desktop-Java-Laufzeit enthält einen Interpreter und zwei Compiler).
Unabhängig davon, wie der Code ausgeführt wird, sollte er sich gleich verhalten. Das Zuweisen und Freigeben von Speicher ist eine Aufgabe für die Laufzeit (genau wie das Öffnen von Dateien, das Starten von Threads usw.). In deiner Sprache schreibst du einfach
new X()
oder gleich. Die Sprachspezifikation sagt, was passieren soll und die Laufzeit tut es.Ein Teil des freien Speichers wird zugewiesen, der Konstruktor wird aufgerufen usw. Wenn nicht genügend Speicher vorhanden ist, wird der Garbage Collector aufgerufen. Da Sie sich bereits in der Laufzeit befinden, bei der es sich um einen nativen Code handelt, spielt die Existenz eines Interpreters keine Rolle.
Es gibt wirklich keine direkte Verbindung zwischen der Interpretation von Code und der Garbage Collection. Es ist nur so, dass Low-Level-Sprachen wie C für die Geschwindigkeit und feinkörnige Steuerung von allem ausgelegt sind, was nicht gut zu der Idee eines nicht-nativen Codes oder eines Garbage Collectors passt. Es gibt also nur eine Korrelation.
Dies traf in alten Zeiten zu, als der Java-Interpreter sehr langsam und der Garbage Collector eher ineffizient war. Heutzutage sind die Dinge anders und das Sprechen über eine interpretierte Sprache hat keinen Sinn mehr.
(*) Zumindest wenn es um allgemeinen Code geht, Bootloader und ähnliches weglassen.
quelle
java myprog
ist so viel oder so wenig nativ wiegrep myname /etc/passwd
oderld.so myprog
: Es ist eine ausführbare Datei (was auch immer das bedeutet), die ein Argument entgegennimmt und Operationen mit den Daten ausführt.Die Details variieren zwischen den Implementierungen, im Allgemeinen ist dies jedoch eine Kombination der folgenden:
Bei der inkrementellen und gleichzeitigen GC müssen der kompilierte Code und der GC zusammenarbeiten, um einige Invarianten beizubehalten. In einem Kopiersammler kopiert der GC beispielsweise Live-Daten von Platz A nach Platz B und hinterlässt dabei den Müll. Für den nächsten Zyklus werden A und B umgedreht und wiederholt. Eine Regel kann daher sein, dass jedes Mal, wenn das Benutzerprogramm versucht, auf ein Objekt in Raum A zu verweisen, dies erkannt wird und das Objekt sofort in Raum B kopiert wird, wo das Programm weiterhin darauf zugreifen kann. Eine Weiterleitungsadresse verbleibt im Bereich A, um dem GC anzuzeigen, dass dies geschehen ist, damit alle anderen Verweise auf das Objekt aktualisiert werden, wenn sie verfolgt werden. Dies wird als "Lesesperre" bezeichnet.
GC-Algorithmen wurden seit den 60er Jahren untersucht und es gibt umfangreiche Literatur zu diesem Thema. Google, wenn Sie weitere Informationen wünschen.
quelle