Ich arbeite an einem großen C ++ - Projekt. Es besteht aus einem Server, der eine REST-API verfügbar macht und eine einfache und benutzerfreundliche Oberfläche für ein sehr breites System bietet, das viele andere Server umfasst. Die Codebasis ist ziemlich groß und komplex und hat sich im Laufe der Zeit ohne ein angemessenes Design im Voraus entwickelt. Meine Aufgabe ist es, neue Funktionen zu implementieren und den alten Code zu überarbeiten / zu reparieren, um ihn stabiler und zuverlässiger zu machen.
Derzeit erstellt der Server eine Reihe langlebiger Objekte, die beim Beenden des Prozesses weder beendet noch entsorgt werden. Dies macht Valgrind für die Lecksuche fast unbrauchbar, da es unmöglich ist, zwischen den Tausenden (fragwürdig) legitimen Lecks und den "gefährlichen" zu unterscheiden.
Meine Idee ist es, sicherzustellen, dass alle Objekte vor der Kündigung entsorgt werden. Als ich diesen Vorschlag machte, lehnten meine Kollegen und mein Chef mich ab und wiesen darauf hin, dass das Betriebssystem diesen Speicher sowieso freigeben wird (was für alle offensichtlich ist) und die Objekte entsorgen verlangsamt das Herunterfahren des Servers (was im Moment im Grunde ein Aufruf an ist std::exit
). Ich antwortete, dass ein "sauberes" Herunterfahren nicht unbedingt bedeutet, dass man es verwenden muss. Wir können immer anrufen std::quick_exit
oder nur kill -9
den Prozess, wenn wir uns ungeduldig fühlen.
Sie antworteten: "Die meisten Linux-Daemons und -Prozesse machen sich beim Herunterfahren nicht die Mühe, Speicherplatz freizugeben." Ich kann das zwar sehen, aber es stimmt auch, dass unser Projekt ein genaues Speicher-Debugging benötigt, da ich bereits Speicherbeschädigungen, doppelte Freigaben und nicht initialisierte Variablen gefunden habe.
Was sind deine Gedanken? Verfolge ich ein sinnloses Unterfangen? Wenn nicht, wie kann ich meine Kollegen und meinen Chef überzeugen? Wenn ja, warum und was soll ich stattdessen tun?
Antworten:
Fügen Sie dem Serverprozess einen Schalter hinzu, der bei Valgrind-Messungen verwendet werden kann, bei denen der gesamte Speicher freigegeben wird. Sie können diesen Schalter zum Testen verwenden. Die Auswirkungen sind während des normalen Betriebs minimal.
Wir hatten einen langen Prozess, der einige Minuten dauern würde, um Tausende von Objekten freizugeben. Es war viel effizienter, einfach auszusteigen und sie sterben zu lassen. Wie Sie angeben, war es leider schwierig, echte Speicherlecks mit Valgrind oder anderen Tools zu erkennen.
Dies war ein guter Kompromiss für unsere Tests, ohne die normale Leistung zu beeinträchtigen.
quelle
Der Schlüssel hier ist folgender:
Dies impliziert ziemlich direkt, dass Ihre Codebasis aus nichts anderem als Hoffnung und Zeichenfolge zusammengeschustert ist. Kompetente C ++ - Programmierer haben keine doppelten Freiheiten.
Sie verfolgen absolut ein sinnloses Unterfangen - wie in, Sie sprechen ein winziges Symptom des eigentlichen Problems an, nämlich dass Ihr Code ungefähr so zuverlässig ist wie das Apollo 13-Servicemodul.
Wenn Sie Ihren Server korrekt mit RAII programmieren, treten diese Probleme nicht auf und das Problem in Ihrer Frage wird behoben. Außerdem wird Ihr Code möglicherweise von Zeit zu Zeit korrekt ausgeführt. Somit ist es eindeutig die beste Option.
quelle
Ein guter Ansatz wäre, die Diskussion mit Ihren Kollegen durch Klassifizierung einzugrenzen. Bei einer großen Codebasis gibt es sicherlich nicht einen einzigen Grund, sondern mehrere (identifizierbare) Gründe für langlebige Objekte.
Beispiele:
Langlebige Objekte, auf die von niemandem verwiesen wird (echte Lecks). Es ist ein Programmierlogikfehler. Beheben Sie Probleme mit niedrigerer Priorität, es sei denn, sie sind dafür verantwortlich, dass Ihr Speicherbedarf im Laufe der Zeit wächst (und die Qualität Ihrer Anwendungen beeinträchtigt). Wenn sie Ihren Speicherbedarf im Laufe der Zeit vergrößern, beheben Sie sie mit höherer Priorität.
Langlebige Objekte, auf die immer noch verwiesen wird, die jedoch aufgrund der Programmlogik nicht mehr verwendet werden, die jedoch Ihren Speicherbedarf nicht vergrößern. Überprüfen Sie den Code und versuchen Sie, die anderen Fehler zu finden, die dazu führen. Fügen Sie der Codebasis Kommentare hinzu, wenn es sich um eine absichtliche (Leistungs-) Optimierung handelt.
Langlebige Objekte "by design". Singleton-Muster zum Beispiel. Sie sind in der Tat schwer zu beseitigen, insbesondere wenn es sich um eine Multithread-Anwendung handelt.
Recycelte Objekte. Langlebige Objekte müssen nicht immer schlecht sein. Sie können auch vorteilhaft sein. Anstatt Hochfrequenzspeicherzuweisungen / -freigaben zu haben, kann das Hinzufügen derzeit nicht verwendeter Objekte zu einem Container, aus dem gezogen werden soll, wenn ein solcher Speicherblock erneut benötigt wird, dazu beitragen, eine Anwendung zu beschleunigen und eine Heap-Fragmentierung zu vermeiden. Sie sollten zum Zeitpunkt des Herunterfahrens leicht zu lösen sein, möglicherweise in einem speziellen "Instrumentation / Checked" -Bau.
"freigegebene Objekte" - Objekte, die von mehreren anderen Objekten verwendet (referenziert) werden und von denen niemand genau weiß, wann sie gespeichert werden, um sie freizugeben. Ziehen Sie in Betracht, sie in Objekte mit Referenzzählung umzuwandeln.
Sobald Sie die wahren Gründe für diese nicht freigegebenen Objekte klassifiziert haben, ist es viel einfacher, eine Einzelfalldiskussion einzugeben und eine Lösung zu finden.
quelle
Meiner Meinung nach sollten die Lebensdauern dieser Objekte niemals einfach hergestellt und sterben gelassen werden, wenn das System heruntergefahren wird. Dies stinkt nach globalen Variablen, von denen wir alle wissen, dass sie schlecht, schlecht, schlecht, schlecht sind. Gerade im Zeitalter intelligenter Zeiger gibt es keinen anderen Grund als Faulheit. Aber was noch wichtiger ist, es erhöht die technische Verschuldung Ihres Systems, mit der sich möglicherweise eines Tages jemand befassen muss.
Die Idee von "technischer Verschuldung" ist, dass, wenn Sie eine Verknüpfung wie diese verwenden, jemand den Code in Zukunft ändern möchte (sagen wir, ich möchte den Client in den "Offline-Modus" oder "Schlafmodus" versetzen können ", oder ich möchte in der Lage sein, Server zu wechseln, ohne den Prozess neu zu starten.) Sie müssen sich die Mühe machen, das zu tun, was Sie überspringen. Aber sie werden Ihren Code beibehalten, sodass sie nicht annähernd so viel darüber wissen wie Sie. Sie werden also viel länger brauchen (ich spreche nicht 20% länger, ich spreche 20x länger!). Selbst wenn Sie es sind, werden Sie seit Wochen oder Monaten nicht mehr an diesem bestimmten Code gearbeitet haben, und es wird viel länger dauern, die Spinnweben abzuwischen, um ihn korrekt zu implementieren.
In diesem Fall scheint es eine sehr enge Kopplung zwischen dem Serverobjekt und den "langlebigen" Objekten zu geben. Es gibt Zeiten, in denen ein Datensatz die Verbindung zum Server überleben könnte (und sollte). Das Verwalten jeder einzelnen Änderung an einem Objekt auf einem Server kann unerschwinglich teuer sein. Daher ist es normalerweise besser, wenn die erzeugten Objekte tatsächlich auf das Serverobjekt übertragen werden, und Aufrufe zum Speichern und Aktualisieren, die den Server tatsächlich ändern. Dies wird üblicherweise als aktives Aufzeichnungsmuster bezeichnet. Sehen:
http://en.wikipedia.org/wiki/Active_record_pattern
In C ++ hätte jeder aktive Datensatz ein schwaches_ptr zum Server, was die Dinge intelligent auslösen könnte, wenn die Serververbindung dunkel wird. Diese Klassen können je nach Ihren Anforderungen entweder träge oder stapelweise ausgefüllt werden. Die Lebensdauer dieser Objekte sollte jedoch nur dort liegen, wo sie verwendet werden.
Siehe auch:
Ist es Zeitverschwendung, Ressourcen freizugeben, bevor ich einen Prozess beende?
Das andere
quelle
This reeks of global variables
Wie geht man von "Es gibt Tausende von Objekten, die freigegeben werden müssen" zu "Sie müssen global sein"? Das ist ein ziemlicher logischer Sprung.Wenn Sie leicht erkennen können, wo die Objekte zugewiesen werden, die auf unbestimmte Zeit überleben sollen, besteht eine Möglichkeit darin, sie mithilfe eines alternativen Zuordnungsmechanismus zuzuweisen, sodass sie entweder nicht in einem Valgrind-Leckbericht angezeigt werden oder nur als einzelne Zuordnung erscheinen.
Falls Sie mit der Idee nicht vertraut sind, finden Sie hier einen Artikel zum Impotenzen der benutzerdefinierten Speicherzuweisung in C ++ . Beachten Sie jedoch, dass Ihre Lösung möglicherweise einfacher ist als die Beispiele in diesem Artikel, da Sie das Löschen überhaupt nicht durchführen müssen !
quelle