1 Punkte von GN⁺ 2 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • zig build ist intern nun in einen Configurer- und einen Maker-Prozess aufgeteilt, und der von build.zig erzeugte Build-Graph wird in eine binäre Konfigurationsdatei serialisiert
  • Der Elternprozess cached die Konfigurationsdatei und kompiliert den Maker asynchron im Release-Modus; dank globalem Cache wird der Maker pro Zig-Version nur einmal kompiliert
  • Bei Änderungen an der build.zig des Nutzers wird nicht mehr das gesamte Build-System mitkompiliert, wodurch sich der Zeitaufwand für zusätzliche Funktionen wie --watch, --fuzz und --webui verringert
  • Die durchschnittliche Ausführungszeit von zig build --help sank von 150ms auf 14.3ms, und auch CPU-Zyklen, Instruktionszahl und Cache-Referenzen gingen um 94–96 % zurück
  • Die meisten APIs bleiben kompatibel, aber direktes Beobachten von b.args wird durch addPassthruArgs() ersetzt; Zig 0.17.0 ist in einigen Wochen geplant

Änderungen an der Architektur des Build-Systems

  • Ein großer Branch wurde gemergt, wodurch zig build intern in einen Configurer-Prozess und einen Maker-Prozess aufgeteilt wurde
  • In der bisherigen Architektur wurden die Datei build.zig und die gesamte Implementierung des Build-Systems als ein großer Prozess im Debug-Modus kompiliert; build.zig erzeugte dabei den Build-Graphen im Speicher, den anschließend der „Build Runner“ ausführte
  • In der neuen Architektur wird build.zig als kleiner Configurer-Prozess im Debug-Modus kompiliert, und der Build-Graph wird in eine binäre Konfigurationsdatei serialisiert
  • Der übergeordnete zig build-Prozess erkennt die Konfigurationsdatei, cached sie für den nächsten Lauf und kompiliert asynchron den Maker im Release-Modus, der die Ausführung des Build-Graphen übernimmt
  • Sobald Konfigurationsdatei und Maker-Kompilierung bereit sind, wird der Maker mit der Konfigurationsdatei ausgeführt; dank globalem Cache muss der Maker pro zig version nur einmal kompiliert werden

Auswirkungen auf die Geschwindigkeit

  • Das zentrale Ziel ist die Beschleunigung von zig build; bei Änderungen an der build.zig des Nutzers wird nicht mehr das gesamte Build-System mitkompiliert
  • Mit der Einführung von --watch, --fuzz und --webui wird es umso wichtiger, dass zusätzliche Funktionen des Build-Systems die Laufzeit von zig build nicht mit anwachsen lassen
  • Wenn festgestellt wird, dass sich nichts geändert hat, kann die bisherige Konfiguration wiederverwendet werden, ohne die build.zig-Logik erneut auszuführen
  • Selbst wenn man beispielsweise -freference-trace zur zig build-Kommandozeile hinzufügt, muss die build.zig-Logik nicht unnötig erneut ausgeführt werden
  • Da der Prozess, der den eigentlichen Build-Graphen ausführt, mit aktivierten Optimierungen kompiliert wird, wird auch die Ausführungsphase schneller

Benchmark-Ergebnisse

  • Die durchschnittliche Ausführungszeit von zig build --help sank von 150ms in der alten Architektur auf 14.3ms in der neuen
  • Die Wall-Clock-Zeit sank von 150ms auf 14.3ms, also um 90.4 %, und die CPU-Zyklen fielen von 593M auf 24.1M, also um 95.9 %
  • Die Instruktionszahl sank von 995M auf 43.7M, also um 95.6 %, und die Cache-Referenzen gingen von 25.8M auf 1.46M, also um 94.3 % zurück
  • Der Peak-RSS sank von 84.8MB auf 78.5MB, also um 7.4 %
  • Der größte Unterschied entsteht dadurch, dass nicht mehr bei jedem zig build-Befehl die build.zig-Logik ausgeführt wird, sondern stattdessen die gecachte serialisierte Konfiguration wiederverwendet wird

Auswirkungen auf Werkzeuge und Kompatibilität

  • Drittanbieter-Tools wie ZLS können profitieren, indem sie statt eines geforkten und gepflegten Build-Runners die serialisierte Konfigurationsdatei direkt verarbeiten
  • Die internen Mechanismen haben sich stark verändert, aus API-Sicht bleibt jedoch größtenteils Kompatibilität erhalten
  • Ausnahmen betreffen die im gemergten PR dokumentierten Änderungen

Wichtige Breaking Changes

  • Die Änderung, auf die viele Nutzer am ehesten stoßen werden, ist die Entfernung des Musters, b.args direkt zu beobachten
  • Bisheriger Code:
if (b.args) |args| {
    run_cmd.addArgs(args);
}
  • Neuer Code:
run_cmd.addPassthruArgs();
  • Durch diese Änderung kann das Build-Skript diese Argumente nicht mehr direkt beobachten; damit wird eine Funktion entfernt
  • Dafür muss das Build-Skript bei Änderungen an diesen Argumenten nicht mehr erneut aus dem Quelltext gebaut werden

Tests und Release-Zeitplan

  • Nutzer, die Einfluss auf die Richtung von Zig nehmen möchten, können ihre Projekte auf eine Entwicklerversion anheben, um die neuen Änderungen zu testen und Feedback zu geben
  • Zig 0.17.0 soll in einigen Wochen veröffentlicht werden
  • Es blieb keine Zeit, vorher zu testen; selbst wenn Builds mit 0.17.0 kaputtgehen, wird es noch genügend Gelegenheit geben, Korrekturen in das Tag 0.17.1 aufzunehmen

1 Kommentare

 
GN⁺ 2 시간 전
Hacker-News-Kommentare
  • Ich habe etwas Code auf Zig 0.16.0 portiert und war mit dem Ergebnis ziemlich zufrieden.
    Die betroffenen Bereiche waren ziemlich umfangreich, aber die Änderungen selbst waren gut und wirken wie ein Schritt in eine Richtung, die der Zukunft der Sprache zugutekommt.
    Vor allem der neue Ein-/Ausgabe-Mechanismus ermöglicht in Single-Thread-, Multi-Thread- und Event-Loop-Implementierungen effizienteren Code in einer sehr sauberen Form.
    Wenn du Zig seit 0.16.0 noch nicht ausprobiert hast, solltest du es dir unbedingt ansehen. Diese Release Notes waren extrem lang.
    https://ziglang.org/download/0.16.0/release-notes.html

    • Ich würde es noch nicht als „sehr effizient“ bezeichnen. Io ist immer noch dynamisch dispatcht und es gibt mehrere Ebenen indirekter Referenzen.
      Soweit ich weiß, ist es langsamer als vorher.
      Ich erwarte, dass in den nächsten Releases das Problem gelöst wird, dass „das Dispatch-Ziel zur Compile-Zeit bekannt ist, aber trotzdem dynamisch bleibt“, wodurch der Effizienzverlust reduziert werden dürfte.
    • Es war etwas schwer zu verstehen, wie der neue Ein-/Ausgabe-Mechanismus, insbesondere async/await, funktioniert.
      Wenn man io.async in einer Event-Loop-basierten IO-Implementierung aufruft, nehme ich an, dass intern so etwas wie ein „Task“ gestartet wird und das Future dessen Handle ist.
      Was ich nicht verstehe, ist der Teil, wenn future.await(io) aufgerufen wird. Unterbricht die IO-Implementierung die aktuelle Funktion irgendwie und setzt sie fort, sobald das Future aufgelöst ist? Bedeutet das dann, dass alle Funktionen in Zig stacklose Coroutines sind?
    • Irgendwann werde ich solche neuen Features vielleicht nutzen können, aber wegen Stabilität und weniger getesteter Targets verwende ich im Zig-Build .use_llvm = true.
  • Nach ein paar Monaten mit Zig bin ich überzeugt, dass es eine hervorragende Sprache für Tools ist.
    Sie eignet sich großartig, um Ideen frei und grob auszuprobieren, und an jedem Punkt, an dem man hängen bleiben könnte, gibt es meist schon eine durchdachte und angenehme Lösung von den Machern.
    Trotzdem wird einem keine „richtige“ Art der Sprachverwendung aufgezwungen.
    Für mich ist sie inzwischen die Standard-Sprache zum „Basteln in der Garage“.

    • Dafür, dass keine „richtige“ Verwendung aufgezwungen werde, sind ungenutzte Variablen nicht erlaubt und mehrzeilige Kommentare gibt es auch nicht.
      Für mich ist das ein ziemlich großes Produktivitätsproblem.
    • Ist es wirklich so gut?
      Meine Standard-Sprache zum „Basteln in der Garage“ ist Python: leichte Syntax, unaufdringliches Nutzungserlebnis, umfangreiche Standardbibliothek, und was fehlt, gibt es als Paket.
      Was ist der Vorteil von Zig?
    • Mein größter Stolperstein dabei ist, dass für mich Mojo wahrscheinlich die Standard-Sprache zum Experimentieren wäre.
    • Es ist definitiv eine großartige Sprache zum Experimentieren, aber das Zig-Team und die Community haben sehr starke Vorlieben dafür, wie man die Sprache „richtig“ verwendet.
  • Nachdem ich ein Interviewvideo mit Andrew Kelley gesehen habe, wollte ich Zig lernen: https://www.youtube.com/watch?v=iqddnwKF8HQ

    • Ich respektiere Andrew wirklich und nutze Zig auch gern, aber dieses Interview war furchtbar.
      Andrews Antworten waren okay, aber die gesamte Stimmung wirkte viel zu schmeichlerisch.
  • Ich wollte Zig ausprobieren, aber die Sprache verändert sich noch zu schnell.
    Mit jedem Release werden APIs gebrochen, daher war es schwer, gleichzeitig die Sprache zu lernen, das Build-System zu debuggen und das umzusetzen, was ich eigentlich bauen wollte.
    Nach dem JetBrains-Interview möchte ich es zwar noch einmal versuchen, aber wahrscheinlich warte ich bis 1.0.

  • Ich denke schon lange über eine Idee nach, die ich duale Programmierung nenne.
    Dabei besteht der Stack aus genau zwei Sprachen: einer High-Level-Sprache und einer Low-Level-Sprache.
    Man schreibt so viel wie möglich in der High-Level-Sprache und geht nur dann in die Low-Level-Sprache herunter, wenn es nötig ist.
    Das Problem ist, dass man sich, wenn man die Low-Level-Sprache nicht ohnehin sehr gut beherrscht, wahrscheinlich erst wieder in sie einarbeiten muss, bevor man auf dieser Ebene arbeiten kann.
    Deshalb sind C++ oder Rust schwieriger zu schreiben als C, und für mich ist C die Standardwahl. Aber C hat bekanntlich seine Probleme.
    Zig scheint diese passende Mitte gut auszufüllen: einfach genug, um auch nach einer langen Pause schnell wieder hineinzufinden, aber mit modernen Werkzeugen, die das Programmieren erleichtern.

    • Die Idee ist interessant, aber ich sehe es genau andersherum.
      Ich mache so viel wie möglich in der Low-Level-Sprache und steige nur dann in eine High-Level-Sprache auf, wenn der Komfort den Preis wert ist.
      Roc macht das möglich. Jedes Programm hat eine Plattform, die in einer Low-Level-Sprache geschrieben ist, und das Roc-Programm verwendet die API, die diese Plattform bereitstellt.
      https://www.roc-lang.org/
      Wie man die Balance zwischen High-Level und Low-Level hält, kann natürlich jeder selbst entscheiden.
    • Soweit ich weiß, wird SpaCy so entwickelt.
      Die Modelle sind in Cython implementiert, und die benutzerseitige API wird in Python angeboten.
    • C# kommt diesem Ziel ziemlich nahe.
    • Man kann auch einfach eine Sprache wie Rust verwenden, die sowohl High-Level- als auch Low-Level-Programmierung gut beherrscht.
      Ich mache inzwischen alles mit Rust, und vor allem dank seines an OCaml erinnernden Typsystems habe ich bisher noch nichts gefunden, was ich damit nicht machen könnte.
    • Früher war eine häufige Kombination C und Lua.
      Lua wurde als eingebettete Sprache konzipiert, ist gut für Interoperabilität und zugleich so High-Level, dass sie leicht zu verwenden ist.
      Dass Factorio Lua als Skriptsprache nutzt, hat seinen Grund.
  • Was mir an der Zig-Entwicklung gefällt, ist, dass unglaublich viel Aufwand in Werkzeuge und die Feedback-Schleife für Entwickler gesteckt wird, statt einfach nur Sprachfeatures hinzuzufügen.
    Eine neue Sprache kann eine Zeit lang auch ohne irgendein Feature überleben.
    Aber wenn Kompilieren, Linken und das Aktualisieren von Abhängigkeiten sich jedes Mal langsam anfühlen, ist das Überleben sehr viel schwieriger.
    Der Fokus darauf, den Entwicklungszyklus nicht in Sekunden, sondern in Millisekunden zu messen, wirkt langfristig wie eine gute Wette.

  • Es überrascht mich, dass „0.17.0 in ein paar Wochen veröffentlicht werden soll“.
    Hat 0.16 nicht mehr als ein Jahr gedauert?
    So ein schnelles 0.17-Release hätte ich nicht erwartet, und ich freue mich sehr, das heute zu erfahren.

  • Klingt nach guten Nachrichten. Die Kompilierzeit von Zig ist schon jetzt hervorragend, und mit dieser Änderung wird sie wohl noch besser

    • Meiner Erfahrung nach ist diese Aussage bislang eher näher am Ziel als an der Realität.
      Es ist klar ein wichtiges Ziel, und auch die Meilensteine dafür sind klar, aber in der Praxis ist es schwer, die schmerzhaft langen Wartezeiten beim ersten Kompilieren eines leeren Projekts oder wenn ZLS nach direnv allow erneut gebaut wird, als „hervorragend“ zu bezeichnen
    • Selbst wenn ich nur eine Datei mit einem Dummy-Test anlege und zig test file.zig -OReleaseSafe ausführe, dauert das auf meinem Rechner einige Sekunden.
      Und jedes Mal, wenn ich die Datei ändere, dauert es wieder genauso lange. Ich nutze 0.16 oder master, die Toolchain ist also nicht veraltet, und ich bin unter Linux.
      Die Zig-Sprache selbst ist wirklich angenehm zu benutzen, aber ich finde, Compiler und Standardbibliothek werden nicht konservativ genug weiterentwickelt.
      Wenn man mit Hello World anfängt, stößt man auf solche Probleme vielleicht nicht, aber für Fuzz-Tests oder Benchmarks möchte man eben optimierte Binärdateien ausführen.
      Dann wird schon das Kompilieren von vergleichsweise wenig Code extrem frustrierend.
      Zum Vergleich: Ich halte Zig als Sprache zwar für deutlich besser als Rust/C++/C, aber solche Probleme treten bei Rust/C++/C praktisch kaum auf. Bei C/C++ setze ich dabei clang/gcc/ninja usw. voraus.
      Auf demselben Rechner kann ich mit Ninja/Python/clang ein C++-Projekt mit rund 10.000 Zeilen konfigurieren, bauen (-O2 oder -O3), testen und das Ganze in 200 ms erledigen
    • Zum Glück kommen Kompiliergeschwindigkeiten wie in den 90ern langsam zurück
  • Es wäre wirklich großartig, wenn Zig einen offiziellen Mechanismus zum Exportieren von Linux-Bibliotheks-Stubs bekäme.
    Zigs Cross-Compilation und die Fähigkeit, beliebige glibc-Versionen als Ziel zu wählen, sind pure Magie.
    Ich nutze diese Magie bereits in einem separaten C++-Build-System, muss aber Umwege gehen, um diese Bibliotheks-Stubs aus Zig herauszubekommen.
    Es wäre schön, wenn das als offizieller Output verfügbar wäre

  • Warum sollte man dafür überhaupt dieses Zeug statt Node.js und TypeScript verwenden wollen?

    • Ich beneide diese Denkweise. Wenn ich auch so wäre, hätte ich wahrscheinlich schon viel mehr Apps veröffentlicht
    • Ich würde empfehlen, Ghostty, das in Zig geschrieben ist, kurz auszuprobieren und es mit einem in JavaScript geschriebenen Terminal wie Hyper zu vergleichen
    • Sind das nicht komplett unterschiedliche Einsatzbereiche?
    • Wenn das, was man macht, gut zu Node und TypeScript passt, gibt es meiner Meinung nach außer zum Lernen oder aus Neugier keinen Grund, Zig zu verwenden.
      Wenn man nicht den letzten Tropfen an Performance sowie Speicherlayout und Kontrolle braucht, hat Zig eher mehr Nachteile.
      Für CRUD oder „Enterprise“-Arbeiten und Websites bringt Zig fast keine Vorteile
    • In eingebetteten Systemen reicht der Speicher nicht aus, um Node auszuführen.
      Ein kompiliertes Zig-Programm kann ohne Abhängigkeiten nur wenige KB groß sein.
      Array-Zugriffe in einer Low-Level-Sprache lassen sich mit SIMD und Parallelisierung optimieren und können um mehrere Größenordnungen schneller sein als dieselbe Arbeit in JavaScript.
      Bei Textverarbeitung, Bildbearbeitung, Videobearbeitung, Hashing usw. ist der Unterschied groß.
      Gründe, JavaScript nicht zu verwenden, gibt es praktisch unendlich viele