Low-Level-Optimierung und Zig
(alloc.dev)- Low-Level-Optimierung lässt sich in der Sprache Zig leicht umsetzen
- Der Compiler führt Optimierungen in den meisten Situationen gut aus, aber manchmal muss die Absicht des Programmierers klar vermittelt werden, um bessere Leistung zu erzielen
- Zig unterstützt mit Compile-Time-Ausführung (
comptime) die Erzeugung hochperformanten Codes und starkes Metaprogramming - Im Vergleich zu Rust ermöglicht Zig durch Annotationen und eine explizite Codestruktur präzisere Optimierungen
- Bei wiederholten Operationen wie String-Vergleichen kann mit
comptimebesserer Assembly-Code erzeugt werden als mit gewöhnlichen Funktionen
Optimierung und Zig
Wie die berühmte Warnung sagt: „Alles ist möglich, aber das Interessante bekommt man nicht leicht.“ Entsprechend gehört die Optimierung von Programmen immer zu den zentralen Interessen von Entwicklern. Für die Kosten von Cloud-Infrastrukturen, geringere Latenz und die Vereinfachung von Systemen ist Code-Optimierung unverzichtbar. Dieser Artikel erklärt vor allem das Konzept der Low-Level-Optimierung in Zig und die Stärken von Zig.
Kann man dem Compiler vertrauen?
- Im Allgemeinen hört man oft den Rat „Vertrau dem Compiler“, in der Praxis kommt es jedoch vor, dass der Compiler anders als erwartet arbeitet oder sogar die Sprachspezifikation verletzt
- Höhere Programmiersprachen erschweren es, Absicht (intent) klar zu vermitteln, was entsprechende Performance-Einschränkungen mit sich bringt
- Low-Level-Sprachen können dem Compiler dank der Explizitheit des Codes die für Optimierungen nötigen Informationen liefern; vergleicht man etwa die Funktion
maxArrayin JavaScript und Zig, übermittelt Zig klare Informationen zu Typen, Alignment und möglichem Aliasing nicht erst zur Laufzeit, sondern bereits zur Compile-Time - Schreibt man dieselbe
maxArray-Operation in Zig und Rust, erhält man fast identischen hochperformanten Assembly-Code, aber je besser sich die Absicht ausdrücken lässt, desto besser fällt das Optimierungsergebnis aus - Da man der Compiler-Leistung jedoch nicht immer vertrauen kann, sollte man in Engpässen Code und Compiler-Ergebnis direkt prüfen und nach Optimierungsmöglichkeiten suchen
Die Rolle von Zig
- Dank Eigenschaften wie präziser Explizitheit, umfangreichen Builtins, Pointern und Annotationen,
comptimesowie klar definiertem Illegal Behavior kann Zig optimierten Code ohne abstrakte Zusatzinformationen erzeugen - Rust garantiert durch sein Speichermodell grundsätzlich, dass Argumente kein Aliasing haben, während in Zig Annotationen wie
noaliasexplizit nötig sind - Selbst wenn man nur LLVM IR als Maßstab nimmt, ist das Optimierungsniveau von Zig hoch
- Vor allem ist Zigs
comptime(Compile-Time-Ausführung) ein mächtiges Optimierungswerkzeug
Was ist comptime?
- Zigs
comptimewird für Codegenerierung, das Einbetten konstanter Werte und die Erzeugung typbasierter generischer Strukturen genutzt und spielt eine wichtige Rolle für bessere Laufzeitleistung - Mit
comptimelässt sich Metaprogramming umsetzen - Anders als Makros in C/C++ oder das Macro-System von Rust ist
comptimekeine eigene Syntax, sondern normaler Code comptime-Code verändert den AST nicht direkt, kann aber für alle Typen zur Compile-Time prüfen, anwenden und erzeugen- Die Flexibilität von
comptimehat auch Verbesserungen in anderen Sprachen wie Rust beeinflusst und ist natürlich in die Sprache Zig integriert
Grenzen von comptime
- Einige Macro-Funktionen wie Token-Pasting lassen sich durch Zigs
comptimenicht ersetzen - Da Zig Lesbarkeit des Codes betont, sind das Erzeugen von Variablen außerhalb des Gültigkeitsbereichs oder Makrodefinitionen dieser Art nicht erlaubt
- Stattdessen gibt es für Zigs
comptimebreite Metaprogramming-Anwendungsfälle wie Typ-Reflection, DSL-Implementierung und die Optimierung von String-Parsing
String-Vergleichsoptimierung mit comptime
- Eine gewöhnliche String-Vergleichsfunktion lässt sich in jeder Sprache implementieren, aber wenn in Zig einer der beiden Strings als zur Compile-Time bekannte Konstante vorliegt, kann effizienterer Assembly-Code erzeugt werden
- Ist zum Beispiel ein String immer
"Hello!\n", kann statt auf Byte-Ebene in größeren Blöcken verglichen werden - Mit
comptimelässt sich dafür hochperformanter Code zur Compile-Time erzeugen, etwa mit SIMD-Vektoren, Blockverarbeitung und Optimierungen für Restbytes - So lassen sich nicht nur wiederholte String-Vergleiche, sondern auch verschiedene mappings auf Basis statischer Daten, perfekte Hash-Tabellen, AST-Parser und andere performanceorientierte Implementierungen umsetzen
Fazit
- Zig eignet sich sehr gut für Low-Level-Optimierung und ermöglicht dank expliziter Codestruktur und mächtigem
comptime, Spitzenleistung direkt umzusetzen - Auch im Vergleich zu anderen Sprachen wie Rust bieten Zigs Compile-Time-Programmierung und Explizitheit große Vorteile bei der Entwicklung hochperformanter Software
- Zigs Optimierungsfähigkeit wird auch künftig ein noch wichtigerer Wettbewerbsvorteil sein
1 Kommentare
Hacker-News-Kommentare
bun, wirklich beeindruckt.bunhat mein Leben enorm vereinfacht. Das Rust-basierteuvbietet eine ähnliche Erfahrungfor(;;);muss tatsächlich eine Endlosschleife sein, undloop {}in Rust ebenso. Aber LLVM-Entwickler verhalten sich manchmal so, als würden sie nur C++-Compiler bauen, und wenn Rust sagt „bitte eine Endlosschleife“, wendet LLVM Optimierungen an nach dem Motto „nach C++-Maßstäben kann das nicht passieren“. Dadurch entstehen Probleme. Es wird also eine falsche Optimierung auf die falsche Sprache angewendetcomptimekann man String-Vergleiche in C problemlos inlinen und unrollen. Hier ein Beispieli < x.length, wodurch JIT-Optimierung greift. Insofern ist es ein wenig Nörgelei, wenn auch nur ein kleiner Unterschiedcomptime, den C++-Compiler nicht erraten könnenpurchase.calculate_tax().await.map_err(|e| TaxCalculationError { source: e })?;voller Absicht, aber wie daraus tatsächlich Maschinencode wird, ist nicht vorhersagbar-march=native, Whole-Program-Optimization usw.). Tatsächlich sind Optimierungshinweise wieunreachableauch in C über Spracherweiterungen möglich, und Clang betreibt ebenfalls sehr aggressive Constant Folding. Das heißt: Der Unterschied zwischen Zigscomptimeund der Codegen-Seite von C entsteht oft durch Compiler-Optimierungseinstellungen. TL;DR: Wenn C langsam ist, sollte man zuerst die Compiler-Settings prüfen. Der Kern der Optimierung ist ohnehin LLVMcomptimeund Whole-Program-Compilation, als ich im ursprünglichen Artikel erwartet hatte. Dem stimme ich zu. Zur Referenz: Virgil unterstützt seit 2006 den Compile-Time-Einsatz der gesamten Sprache und Whole-Program-Compilation. Virgil zielt nicht auf LLVM, daher ist ein Geschwindigkeitsvergleich letztlich ein Vergleich der Backends. Durch diesen Ansatz kann Virgil Methodenaufrufe vorab statisch binden (devirtualisieren), ungenutzte Felder/Objekte möglichst entfernen, Konstanten sogar bis in Feld-Heap-Objekte propagieren und perfekt spezialisieren, was sehr starke Optimierungen ermöglichtfor-Loop-Syntax von Zig wirkt auf mich viel zu unübersichtlich. Zwei Listen nebeneinanderlegen und ihre Positionen ausrichten zu müssen, tut schon beim Ansehen weh. Ich halte es für einen Fehler, dass moderne Sprachen so viele „magische“ Syntaxformen und Sonderzeichen anhäufen. Ich glaube nicht, dass ich mir das stundenlang ansehen könnte