Physische Lecks
Die Art von Fehlern, die GC anspricht, scheint (zumindest für einen externen Beobachter) die Art von Dingen zu sein, die ein Programmierer, der seine Sprache, Bibliotheken, Konzepte, Redewendungen usw. gut kennt, nicht tun würde. Aber ich könnte mich irren: Ist die manuelle Speicherverwaltung an sich kompliziert?
Wenn ich von der C-Seite komme, die die Speicherverwaltung so manuell und ausgeprägt wie möglich macht, damit wir Extreme vergleichen (C ++ automatisiert die Speicherverwaltung meist ohne GC), würde ich sagen, "nicht wirklich" im Sinne eines Vergleichs mit GC, wenn dies der Fall ist kommt zu undichtigkeiten . Ein Anfänger und manchmal sogar ein Profi kann vergessen, free
für eine gegebene zu schreiben malloc
. Es passiert definitiv.
Es gibt jedoch Tools wie die valgrind
Lecksuche, die bei der Ausführung des Codes sofort erkennen, wann / wo solche Fehler bis auf die genaue Codezeile auftreten. Wenn dies in das CI integriert ist, ist es fast unmöglich, solche Fehler zusammenzuführen und einfach zu korrigieren. Es ist also in keinem Team / Prozess eine große Sache mit vernünftigen Standards.
Zugegeben, es kann einige exotische Fälle von Ausführung geben, die unter dem Radar des Testens free
auftauchen, wenn sie nicht aufgerufen werden, möglicherweise wenn ein obskurer externer Eingabefehler wie eine beschädigte Datei auftritt. In diesem Fall kann das System 32 Bytes oder etwas verlieren. Ich denke, das kann definitiv auch unter recht guten Teststandards und Leckerkennungswerkzeugen passieren, aber es wäre auch nicht ganz so kritisch, ein bisschen Speicher für etwas zu verlieren, das so gut wie nie passiert. Es wird ein weitaus größeres Problem geben, bei dem wir auch in den unten aufgeführten allgemeinen Ausführungspfaden massive Ressourcen verlieren können, was GC nicht verhindern kann.
Es ist auch schwierig, ohne etwas zu tun, das einer Pseudoform von GC ähnelt (z. B. Referenzzählung), wenn die Lebensdauer eines Objekts für eine Form von verzögerter / asynchroner Verarbeitung verlängert werden muss, z. B. durch einen anderen Thread.
Baumelnde Zeiger
Das eigentliche Problem mit mehr manuellen Formen der Speicherverwaltung ist für mich nicht zu übersehen. Wie viele native Anwendungen, die in C oder C ++ geschrieben wurden, sind wirklich undicht? Ist der Linux-Kernel undicht? MySQL? CryEngine 3? Digitale Audio-Workstations und Synthesizer? Leckt Java VM (im nativen Code implementiert)? Photoshop?
Ich denke, wenn wir uns umschauen, sind die am wenigsten geeigneten Anwendungen diejenigen, die mit GC-Schemata geschrieben wurden. Bevor dies jedoch als Slam-on-Garbage-Collection betrachtet wird, tritt bei systemeigenem Code ein erhebliches Problem auf, das überhaupt nicht mit Speicherverlusten zusammenhängt.
Das Thema war für mich immer Sicherheit. Selbst wenn wir uns free
durch einen Zeiger erinnern, werden andere Zeiger auf die Ressource zu baumelnden (ungültigen) Zeigern.
Wenn wir versuchen, auf die Spitzen dieser baumelnden Zeiger zuzugreifen, geraten wir letztendlich in ein undefiniertes Verhalten, obwohl fast immer eine Segfault- / Zugriffsverletzung zu einem harten, sofortigen Absturz führt.
Alle diese nativen Anwendungen, die ich oben aufgeführt habe, weisen möglicherweise ein oder zwei unklare Randbedingungen auf, die in erster Linie aufgrund dieses Problems zu einem Absturz führen können, und es gibt definitiv einen angemessenen Anteil von fehlerhaften Anwendungen, die in nativem Code geschrieben sind, der sehr absturzintensiv und häufig ist zum großen Teil aufgrund dieses Problems.
... und das liegt daran, dass das Ressourcenmanagement schwierig ist, unabhängig davon, ob Sie GC verwenden oder nicht. Der praktische Unterschied besteht häufig darin, dass der Computer aufgrund eines Fehlers, der zu einem Missmanagement der Ressourcen führt, leckt (GC) oder abstürzt (ohne GC).
Ressourcenverwaltung: Garbage Collection
Ein komplexes Ressourcenmanagement ist in jedem Fall ein schwieriger manueller Prozess. GC kann hier nichts automatisieren.
Nehmen wir ein Beispiel, wo wir dieses Objekt haben, "Joe". Auf Joe wird von einer Reihe von Organisationen verwiesen, denen er angehört. Jeden Monat oder so ziehen sie einen Mitgliedsbeitrag von seiner Kreditkarte ab.
Wir haben auch einen Hinweis auf Joe, um sein Leben zu kontrollieren. Nehmen wir an, als Programmierer brauchen wir Joe nicht mehr. Er fängt an, uns zu belästigen, und wir brauchen diese Organisationen nicht mehr, denen er angehört, und verschwenden ihre Zeit damit, sich um ihn zu kümmern. Also versuchen wir, ihn vom Erdboden zu wischen, indem wir seinen Lebenslinienbezug entfernen.
... aber warte, wir verwenden Garbage Collection. Jeder starke Hinweis auf Joe wird ihn in der Nähe halten. Deshalb entfernen wir auch Verweise auf ihn aus den Organisationen, denen er angehört (ihn abbestellen).
... außer Hoppla, wir haben vergessen, sein Zeitschriftenabonnement zu kündigen! Jetzt bleibt Joe im Gedächtnis, belästigt uns und verbraucht Ressourcen, und das Zeitschriftenunternehmen arbeitet jeden Monat an der Bearbeitung von Joes Mitgliedschaft.
Dies ist der Hauptfehler, der dazu führen kann, dass viele komplexe Programme, die mit Garbage Collection-Schemata geschrieben wurden, auslaufen und mehr und mehr Speicher verbrauchen, je länger sie ausgeführt werden und möglicherweise mehr und mehr verarbeitet werden (das Abonnement für wiederkehrende Magazine). Sie vergaßen, einen oder mehrere dieser Verweise zu entfernen, was es dem Müllsammler unmöglich machte, seine Magie zu entfalten, bis das gesamte Programm heruntergefahren wurde.
Das Programm stürzt jedoch nicht ab. Es ist absolut sicher. Es wird nur die Erinnerung auffrischen und Joe wird immer noch verweilen. Für viele Anwendungen ist diese Art von undichtem Verhalten, bei dem immer mehr Speicher / Verarbeitung zum Problem wird, einem harten Absturz weitaus vorzuziehen, insbesondere angesichts der Größe des Arbeitsspeichers und der Rechenleistung, über die unsere Maschinen heutzutage verfügen.
Ressourcenverwaltung: Manuell
Betrachten wir nun die Alternative, bei der wir Zeiger auf Joe und manuelle Speicherverwaltung verwenden, wie folgt:
Diese blauen Links verwalten nicht Joes Leben. Wenn wir ihn vom Erdboden entfernen wollen, fordern wir ihn manuell auf, wie folgt zu zerstören:
Nun, das würde uns normalerweise überall mit baumelnden Zeigern zurücklassen, also lasst uns die Zeiger auf Joe entfernen.
... hoppla, wir haben genau den gleichen Fehler gemacht und vergessen, Joes Abonnement zu kündigen!
Außer jetzt haben wir einen baumelnden Zeiger. Wenn das Zeitschriftenabonnement versucht, Joes monatliche Gebühr zu verarbeiten, explodiert die ganze Welt - normalerweise bekommen wir den schweren Absturz sofort.
Derselbe grundlegende Fehler beim Missmanagement von Ressourcen, bei dem der Entwickler vergessen hat, alle Zeiger / Verweise auf eine Ressource manuell zu entfernen, kann in nativen Anwendungen zu zahlreichen Abstürzen führen. Sie belasten den Speicher nicht, je länger sie normalerweise ausgeführt werden, da sie in diesem Fall häufig regelrecht abstürzen.
Echte Welt
Das obige Beispiel verwendet nun ein lächerlich einfaches Diagramm. Für eine reale Anwendung sind möglicherweise Tausende von Bildern erforderlich, die zusammengefügt werden, um ein vollständiges Diagramm abzudecken. In einem Szenendiagramm sind Hunderte verschiedener Ressourcentypen gespeichert, einige davon mit GPU-Ressourcen verknüpft, andere mit Beschleunigern verknüpft und Beobachter auf Hunderte von Plugins verteilt Beobachten Sie eine Reihe von Entitätstypen in der Szene auf Änderungen, Beobachter, Beobachter, Audios, die mit Animationen synchronisiert sind usw. Es scheint also, als wäre es einfach, den oben beschriebenen Fehler zu vermeiden, aber in der realen Welt ist es im Allgemeinen nicht annähernd so einfach Produktionscodebasis für eine komplexe Anwendung, die Millionen von Codezeilen umfasst.
Die Wahrscheinlichkeit, dass jemand eines Tages die Ressourcen irgendwo in dieser Codebasis falsch verwaltet, ist recht hoch, und diese Wahrscheinlichkeit ist mit oder ohne GC gleich. Der Hauptunterschied besteht darin, was als Ergebnis dieses Fehlers passieren wird. Dies wirkt sich auch potenziell darauf aus, wie schnell dieser Fehler entdeckt und behoben wird.
Crash vs. Leak
Welches ist nun schlimmer? Ein sofortiger Absturz oder ein stilles Gedächtnisleck, in dem Joe auf mysteriöse Weise herumlungert?
Die meisten antworten vielleicht auf Letzteres, aber nehmen wir an, diese Software ist so konzipiert, dass sie stundenlang, möglicherweise tagelang ausgeführt wird, und jeder dieser von uns hinzugefügten Joes und Janes erhöht die Speichernutzung der Software um ein Gigabyte. Es ist keine geschäftskritische Software (Abstürze töten Benutzer nicht wirklich), sondern eine leistungskritische.
In diesem Fall ist ein schwerer Absturz, der beim Debuggen sofort auftritt und auf den von Ihnen begangenen Fehler hinweist, möglicherweise einer undichten Software vorzuziehen, die möglicherweise sogar unter dem Radar Ihres Testverfahrens läuft.
Auf der anderen Seite, wenn es sich um eine unternehmenskritische Software handelt, bei der die Leistung nicht das Ziel ist und die auf keinen Fall abstürzt, ist eine Leckage möglicherweise sogar vorzuziehen.
Schwache Referenzen
Es gibt eine Art Hybrid dieser Ideen in GC-Schemata, die als schwache Referenzen bekannt sind. Mit schwachen Referenzen können wir alle diese Organisationen so einrichten, dass sie Joe schwach referenzieren, ihn jedoch nicht daran hindern, entfernt zu werden, wenn die starke Referenz (Joes Besitzer / Lebensader) wegfällt. Trotzdem haben wir den Vorteil, durch diese schwachen Referenzen feststellen zu können, wann Joe nicht mehr in der Nähe ist, und so eine Art leicht reproduzierbarer Fehler erhalten zu können.
Leider werden schwache Referenzen bei weitem nicht so oft verwendet, wie sie wahrscheinlich verwendet werden sollten, so dass viele komplexe GC-Anwendungen möglicherweise für Lecks anfällig sind, selbst wenn sie möglicherweise weitaus weniger abstürzen als eine komplexe C-Anwendung, z
In jedem Fall hängt es davon ab, wie wichtig es ist, dass Ihre Software Leckagen vermeidet, und ob es sich um ein komplexes Ressourcenmanagement dieser Art handelt oder nicht.
In meinem Fall arbeite ich in einem leistungskritischen Bereich, in dem Ressourcen Hunderte von Megabyte bis Gigabyte umfassen, und wenn Benutzer aufgrund eines Fehlers wie des oben genannten das Entladen anfordern, wird es einem Absturz weniger vorgezogen, diesen Speicher nicht freizugeben . Abstürze sind leicht zu erkennen und zu reproduzieren, was sie häufig zu den beliebtesten Fehlern des Programmierers macht, auch wenn sie die am wenigsten bevorzugten Fehler des Benutzers sind. Viele dieser Abstürze treten mit einem vernünftigen Testverfahren auf, bevor sie den Benutzer überhaupt erreichen.
Das sind jedenfalls die Unterschiede zwischen GC und manueller Speicherverwaltung. Zur Beantwortung Ihrer unmittelbaren Frage würde ich sagen, dass die manuelle Speicherverwaltung schwierig ist, aber nur sehr wenig mit Lecks zu tun hat. Sowohl die GC- als auch die manuelle Speicherverwaltung sind nach wie vor sehr schwierig, wenn die Ressourcenverwaltung nicht trivial ist. Der GC hat hier wohl ein kniffligeres Verhalten, bei dem das Programm anscheinend einwandfrei funktioniert, aber immer mehr Ressourcen verbraucht. Die manuelle Form ist weniger knifflig, wird aber mit Fehlern wie dem oben gezeigten sehr schnell zum Absturz gebracht.