RAII, Rust/Linux – eine Illusion
(kristoff.it)Zusammenfassung
Dies ist ein Artikel, den ich geschrieben habe, nachdem ich den Streit zwischen Rust-Entwicklern und bestehenden Linux-Entwicklern verfolgt habe. Verschiedene Entwickler können jeweils unterschiedliche Coding-Stile haben, doch das Linux-Projekt hat bereits C++ ausgeschlossen und damit in der Vergangenheit auch dessen Coding-Stil und Struktur (RAII) vermieden.
Die Funktionsweise des von Asahi Lina erwähnten Codes ist beim Beenden dieses Programms zu langsam und steht im Widerspruch zu Batch-Verarbeitung, dem grundlegendsten Ansatz beim Entwickeln leistungsorientierter Software. Zum Beispiel ermöglicht die Nutzung von Speicherbereichen für Batch-Verarbeitung, mehrere Lebensdauern als eine zu koordinieren, sodass RAII nicht nötig ist.
Hier sind Materialien, die meine Behauptung stützen. Alle diese Materialien zeigen, warum Batch-Verarbeitung gut ist:
- Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer
- CppCon 2014: Mike Acton "Data-Oriented Design and C++"
- Modern Systems Programming: Rust and Zig - Aleksey Kladov
Daher denke ich, dass Linux RAII niemals übernehmen sollte.
Ich habe diesen Beitrag aus folgendem Grund hierher gebracht: Ich habe mehrfach gesehen, dass koreanische Rust-Entwickler beim Anblick jenes Artikels sehr verärgert wirkten, und deshalb war ich neugierig, was die Leute hier darüber denken. Was meint ihr dazu?
11 Kommentare
Meiner Ansicht nach kann ich den Elitismus mancher Entwickler bis zu einem gewissen Grad verstehen. Aus der Perspektive der Software-„Ingenieurskunst“, insbesondere bei Linux, einer „Software“, die heute im Open-Source-Lager zu den wenigen zählt, die auch mit dem Closed-Source-Lager breit zusammengearbeitet und so zum Fortschritt der Open-Source-Philosophie beigetragen hat, ist es vielleicht genau deshalb so, dass man eine umso exklusivere, ja fast ludditisch wirkende konservative Haltung einnimmt: aus Sorge, dass ungeprüfte Programmierer mit Rust als Banner massenhaft hereindrängen, sich der Kontrolle der zentralen Maintainer des bestehenden Projekts entziehen, überall Code anflicken, die technische Schuld massiv erhöhen und dadurch den Lebenszyklus von Linux verkürzen könnten.
Es ist interessant, dass man eine nicht gerade „offene“ Haltung einnimmt, damit Open Source lange Open Source bleibt.
Ich nutze und empfehle RAII oder ähnliche Formen des Ressourcenmanagements ebenfalls häufig. Denn selbst wenn man nicht einmal genau weiß, was RAII ist, und es gedankenlos einsetzt, entsteht dabei erst einmal „sicherer“ Code.
Wenn man es jedoch nicht richtig versteht und verwendet, produziert man leicht ineffizienten Code, bei dem Dateien dutzendfach geöffnet und wieder geschlossen werden, obwohl ein einziges Öffnen gereicht hätte. Wenn Entwickler Leistung kontinuierlich im Blick behalten und eine solche Kultur im Entwicklungsteam vorausgesetzt ist, kann man meiner Meinung nach auch mit RAII ein vollkommen ausreichendes Leistungsniveau erreichen.
freewird jedes Mal ausgeführt, wenn ein Objekt zerstört wirdfreefreigegeben werden müssen, sammeln und dann gesammelt als Bulk ausführen?Gibt es unter Linux eine Funktion, vielleicht eine API, die dafür sorgt, dass 2 schneller ausgeführt wird als 1?
Ich habe natürlich immer mit 1 gelebt, deshalb verstehe ich es nicht so gut.
Ich möchte nicht wieder zu der Entwicklungserfahrung zurückkehren, nach Abschluss der Entwicklung mit valgrind nach Memory Leaks zu suchen.
Ich weiß es nicht genau, aber auf RAII zu verzichten, klingt so, als wolle man die Leistung beim (Schließen) durch bewusstes Memory-Leaking erhöhen. Ich bin mir nicht sicher, ob das wirklich die richtige Richtung ist.
Wenn ein Entwickler Speicher ohnehin manuell gut verwalten kann, wird er RAII auch gut einsetzen können. Und ein Entwickler, der ohne RAII nicht entwickeln kann, wird den Speicher vermutlich auch nicht manuell verwalten können. Deshalb sehe ich keinen Grund, auf RAII zu verzichten.
Ich wollte wissen, wie viel Zeit
freetatsächlich verbraucht, und habe deshalb zum Testen einfachen Code geschrieben, auch wenn er sich deutlich von realen Workloads unterscheidet. (Verwendet wurden ein Rust-Release-Build sowiestd::alloc::allocundstd::fs::File.)Ich habe 10.000.000 Speicherblöcke unterschiedlicher Größe mit insgesamt etwa 2,5 GB alloziert und dann nur die Zeit für das Freigeben gemessen; das dauerte 1,87 Sekunden. Das entspricht 187 ns pro Block.
Bei Dateien hingegen habe ich nur etwa 10.000 Handles geöffnet und dann nur die Zeit fürs Schließen gemessen; das dauerte rund 9 Sekunden. Das sind 900 µs pro Datei.
(Auf diesem Windows-PC sind Dateioperationen wohl wegen des Virenscanners besonders langsam. Auf einem anderen Windows-Laptop lagen die Werte jeweils bei 400 ns/200 µs, auf einem anderen Linux-PC bei 50 ns/600 ns.)
Als Alternativen zu RAII werden oft Bulk-Verarbeitung oder das absichtliche Leaken von Ressourcen genannt, indem man sich beim Programmende auf das Betriebssystem verlässt. Bei Speicher wäre das leicht möglich.
Bei Ressourcen wie Dateien oder Sockets habe ich jedoch noch keine API zur Bulk-Rückgewinnung gesehen, und wenn man Ressourcen leakt, mag das zwar die Zeit im User-Code verkürzen, aber genau in dem Maß verlängert sich die Zeit, die der Kernel zum Beenden des Prozesses braucht, sodass der Performance-Gewinn gering ist.
Memory-RAII ist relativ gesehen auch nicht so langsam, macht den Einsatz von Arenas nicht unmöglich und verhindert bei Bedarf auch keine absichtlichen Leaks, weshalb es schwerfällt, darin einen guten Grund zu sehen, RAII zu meiden.
Und bei dem langsameren Datei-RAII gibt es weder eine Möglichkeit zur Bulk-Verarbeitung noch eine Möglichkeit, die Kosten zu vermeiden; daher frage ich mich, wie viel besser die Alternativen zu RAII dort tatsächlich sind.
Etwas am Thema vorbei, aber ich habe den Eindruck, dass Einwände gegen RAII und Lifetimes meist nur im Kontext von Speicherressourcen diskutiert werden, wie sie durch
malloc/freerepräsentiert werden.RAII und Lifetimes sind jedoch nicht nur für Speicherallokation nützlich, sondern ganz allgemein für das Modellieren von Ressourcen, die erworben und zurückgegeben werden und während des Besitzes exklusive Zugriffskontrolle benötigen — also nicht nur für OS-Ressourcen wie Dateien, Sockets und Locks, sondern auch für Objektpools, Connection Pools usw.
Auch diese Ressourcen teilen die gleiche Struktur wie
malloc/free, und daher teilen sie auch Probleme derselben Art wie Leaks, use after free und double free.Gerade weil sie dieselbe Struktur teilen, lösen RAII und Lifetimes nicht nur Speicherprobleme, sondern gleichzeitig auch die Probleme solcher Ressourcen; ich denke, dieser Punkt sollte stärker hervorgehoben werden.
In Rust werden zum Beispiel auch bei Datei-Handles use after close und double close bereits zur Compile-Zeit verhindert:
https://play.rust-lang.org/?version=stable&mode=debug&edition=…
Die großen GC-Sprachen verwalten Speicher per GC, führen aber für Ressourcen wie Datei-Handles und Sockets, deren Verwaltung letztlich deterministisch sein muss, zusätzlich Strukturen wie RAII (etwa Javas
try-with-resources-Statement, C#susing-Statement oder Pythonswith-Statement) oder ähnliche Konstrukte (wie Gosdefer) ein. Dadurch gibt es am Ende in einer Sprache mehrere Modi der Ressourcenverwaltung, und ich denke, dass dieser Ansatz womöglich schlechter ist als ein einheitlicherer.Falls Sie damit eine Arena meinen, gibt es in Rust natürlich ebenfalls Arenas, und durch die Entfernung der Arena mittels lifetime lässt sich nach dem „gebündelten Freigeben“ auch der Zugriff auf Elemente der Arena verbieten. Siehe dazu bitte https://crates.io/keywords/arena .
Ich hoffe, dass nach Zig und Rust noch viele weitere Sprachen erscheinen. Aber bisher habe ich noch keine Sprache gesehen, die so passend ist wie Rust. Ich denke eher, dass das Wissen unter Entwicklern, das aus solchen Diskussionen zwischen den Sprachen hervorgeht, nützlich ist. Haha..
Ich bin ebenfalls ein Entwickler, der Rust gewissermaßen als Hauptsprache nutzt. Wütend war ich zwar nicht, aber ich hatte schon den Eindruck, dass hier ein etwas extremes Beispiel herangezogen wird (mit der Formulierung „wenn das Beenden dieses Programms zu langsam ist“ und sogar in dem verlinkten Video wird ein Fall genannt, der nicht direkt mit einem Rust-Projekt zusammenhängt: Beim Beenden von Visual Studio dauert es zu lange, weil die Destruktoren der einzelnen Komponenten aufgerufen werden).
Wenn es aus Performance-Gründen nötig ist, das Clean-up mehrerer Komponenten auf einmal zu verarbeiten, könnte man wohl statt
Dropfür jede einzelne Komponente zu implementieren,Dropauf einem Typ implementieren, der die Lebensdauer dieser Komponenten verwaltet, sodass das Clean-up gesammelt auf einmal ausgeführt wird. Noch besser wäre es, wenn man eine Absicherung einbaut, sodass diese Komponenten nur über die API dieses Typs erzeugt werden können.Natürlich scheint die Sorge des Autors des obigen Textes zu sein, dass, wenn sich die Praxis der Nutzung von RAII in die Linux-Codebasis einschleicht, sich in der Komplexität einer riesigen Codebasis langfristig Code mit sehr impliziten Performance-Risiken ansammeln könnte und am Ende etwas Ähnliches wie bei Visual Studio passiert. Ich denke, das ist ein Punkt, der durchaus berechtigte Sorgen auslöst. Andererseits gibt es, wie auch in anderen Kommentaren erwähnt wurde, die von RAII gebotene Stabilität, sodass die Entscheidung wohl eher ein Trade-off ist.
Beide Seiten haben recht.
Als Analogie: Im Online-Spiel LoL gilt der Champion Azir als High-Tier-Champion mit überragender Stärke im Splitpush, bei der Gebietskontrolle in Teamfights und mit enormem Ult-Wert. Aber das gilt nur in hochgradig eingespielten Pro-Spielen; auf dem Niveau normaler Spieler ist er in der Lane viel zu schwach und insgesamt zu fragil und damit letztlich nur ein Low-Tier-Champion.
Aus der Sicht von Leuten wie Asahi Lina, die zu den obersten 10 % mit Programmier- und Betriebssystemwissen gehören, sind Alternativen zu RAII natürlich besser. Aber in dem Bereich, mit dem sich die übrigen 90 % befassen, gibt es meiner Meinung nach kaum etwas Besseres als RAII oder Rust.
Da einer der großen Gründe, Speicherstabilität/-sicherheit zu gewährleisten, in Sicherheitsproblemen liegt, halte ich Trade-offs jedoch für unvermeidlich.
Ohne RAII würden Entwickler mit vergleichsweise wenig Erfahrung vermutlich massenhaft Bugs produzieren.
Zumindest auf Anwendungsebene, nicht beim OS, ...