- L’intérieur de zig build est scindé en un processus configurer et un processus maker, et le graphe de build créé par
build.zig est sérialisé dans un fichier de configuration binaire
- Le processus parent met en cache le fichier de configuration et compile le maker de façon asynchrone en mode Release ; le maker n’est compilé qu’une seule fois dans le cache global pour chaque version de Zig
- Lorsqu’un
build.zig utilisateur est modifié, il n’est plus nécessaire de recompiler en même temps l’ensemble du système de build, ce qui réduit le coût temporel de l’ajout de fonctionnalités comme --watch, --fuzz, --webui
- Le temps d’exécution moyen de
zig build --help est passé de 150 ms à 14,3 ms, avec une baisse de 94 à 96 % des cycles CPU, du nombre d’instructions et des accès au cache
- La plupart des API restent compatibles, mais l’observation directe de
b.args est remplacée par addPassthruArgs(), et Zig 0.17.0 est prévu dans quelques semaines
Changement d’architecture du système de build
- Une grosse branche a été fusionnée, séparant l’intérieur de
zig build en processus configurer et processus maker
- Dans l’ancienne architecture, le fichier
build.zig et toute l’implémentation du système de build étaient compilés dans un grand processus en mode Debug, puis build.zig construisait le graphe de build en mémoire avant qu’un « build runner » ne l’exécute
- Dans la nouvelle architecture,
build.zig est compilé dans un petit processus configurer en mode Debug, et le graphe de build est sérialisé dans un fichier de configuration binaire
- Le processus parent
zig build détecte ce fichier de configuration, le met en cache pour les exécutions suivantes et compile de façon asynchrone, en mode Release, le maker chargé d’exécuter le graphe de build
- Une fois le fichier de configuration et la compilation du maker prêts, le maker s’exécute en recevant ce fichier ; grâce au cache global, il ne doit être compilé qu’une seule fois par
zig version
Effets sur les performances
- L’objectif principal est d’accélérer
zig build, car lorsqu’un build.zig utilisateur change, il n’est plus nécessaire de recompiler en même temps tout le système de build
- Avec l’arrivée de
--watch, --fuzz, --webui, l’enjeu est aussi d’éviter que le temps de zig build n’augmente à mesure que les fonctionnalités du système de build se multiplient
- Si aucun changement n’est détecté, il est possible de réutiliser la configuration précédente sans relancer la logique de
build.zig
- Par exemple, ajouter
-freference-trace à la ligne de commande de zig build ne relance plus inutilement la logique de build.zig
- Le processus qui exécute réellement le graphe de build étant compilé avec les optimisations activées, l’étape d’exécution est elle aussi plus rapide
Résultats des benchmarks
- Le temps d’exécution moyen de
zig build --help est passé de 150 ms dans l’ancienne architecture à 14,3 ms dans la nouvelle
- Le temps mur est passé de
150 ms à 14,3 ms, soit une baisse de 90,4 %, et les cycles CPU de 593M à 24,1M, soit une baisse de 95,9 %
- Le nombre d’instructions est passé de
995M à 43,7M, soit une baisse de 95,6 %, et les accès au cache de 25,8M à 1,46M, soit une baisse de 94,3 %
- Le pic de RSS a diminué de
84,8MB à 78,5MB, soit 7,4 % de moins
- La plus grande différence vient du passage d’un modèle qui exécutait la logique de
build.zig à chaque commande zig build à un modèle qui réutilise une configuration sérialisée mise en cache
Outils et impact sur la compatibilité
- Des outils tiers comme ZLS peuvent y gagner en consommant directement le fichier de configuration sérialisé, au lieu de continuer à maintenir un fork du build runner
- Même si les mécanismes internes changent profondément, la plupart des API restent compatibles du point de vue utilisateur
- Les exceptions correspondent aux changements répertoriés dans la PR fusionnée
Principaux changements cassants
- Le changement que beaucoup d’utilisateurs risquent le plus de rencontrer est la suppression du pattern consistant à observer directement
b.args
- Ancien code :
if (b.args) |args| {
run_cmd.addArgs(args);
}
run_cmd.addPassthruArgs();
- Avec ce changement, les scripts de build ne peuvent plus observer directement ces arguments, ce qui supprime une fonctionnalité
- En contrepartie, modifier ces arguments ne nécessite plus de reconstruire à nouveau le script de build depuis les sources
Tests et calendrier de sortie
- Les utilisateurs qui veulent influencer l’orientation de Zig peuvent faire passer leur projet sur une version de développement pour tester ces changements et faire remonter leurs retours
- Zig
0.17.0 devrait sortir dans quelques semaines
- Faute de temps, ces changements n’ont pas pu être testés à l’avance ; même si des builds cassent en
0.17.0, il y aura largement le temps d’intégrer des correctifs dans le tag 0.17.1
1 commentaires
Commentaires sur Hacker News
J’ai porté une partie de mon code sur Zig 0.16.0 et le résultat m’a semblé assez satisfaisant
Il y a eu beaucoup de parties affectées, mais les changements eux-mêmes étaient bons et vont dans une direction qui semble prometteuse pour l’avenir du langage
En particulier, le nouveau mécanisme d’entrée/sortie permet d’écrire du code efficace avec une forme élégante aussi bien pour les implémentations mono‑thread, multi‑thread que basées sur une boucle d’événements
Si vous n’avez pas encore essayé Zig depuis la 0.16.0, je vous recommande vivement d’y jeter un œil. Les notes de version étaient extrêmement longues
https://ziglang.org/download/0.16.0/release-notes.html
D’après ce que je sais, c’est plus lent qu’avant
J’espère que les prochaines versions résoudront le problème du « la cible du dispatch est connue à la compilation mais reste quand même dynamique », afin de réduire cette perte d’efficacité
J’imagine que lorsqu’on appelle
io.asyncdans une implémentation d’IO basée sur une boucle d’événements, cela lance en interne une sorte de « tâche », et que le future en est le handleCe que je ne comprends pas, c’est ce qui se passe quand on appelle
future.await(io). L’implémentation IO interrompt-elle d’une manière ou d’une autre la fonction courante, puis la reprend quand le future est résolu ? Si oui, cela veut-il dire que toutes les fonctions de Zig sont des coroutines sans pile ?.use_llvm = truedans les builds ZigAprès avoir utilisé Zig pendant quelques mois, je suis convaincu que c’est un excellent langage pour les outils
C’est agréable à prendre en main pour bricoler librement des idées rapidement, et à chaque point de friction on trouve souvent une solution confortable que ses créateurs ont déjà pensée
Cela dit, il n’impose pas pour autant une manière « correcte » d’utiliser le langage
Pour moi, c’est désormais mon langage par défaut pour « bricoler dans le garage »
Pour moi, c’est un vrai problème de productivité
Mon langage par défaut pour « bricoler dans le garage », c’est Python. Syntaxe légère, usage sans friction, bibliothèque standard riche, et pour le reste il y a des packages pour tout
Quels sont les avantages de Zig ?
Après avoir vu une vidéo d’interview d’Andrew Kelley, j’ai eu envie d’apprendre Zig : https://www.youtube.com/watch?v=iqddnwKF8HQ
Les réponses d’Andrew étaient correctes, mais l’ambiance générale donnait beaucoup trop une impression de complaisance
J’avais envie d’essayer Zig, mais le langage change encore trop vite
À chaque version il y a des ruptures d’API, donc il était difficile de suivre en même temps l’apprentissage du langage, le débogage du système de build et l’implémentation de ce que je voulais réellement créer
L’interview chez JetBrains me donne envie de réessayer, mais j’attendrai probablement la 1.0
Depuis longtemps, je réfléchis à une idée que j’appelle la programmation à deux niveaux
Cela consiste à structurer sa stack autour de deux langages seulement : un langage de haut niveau et un langage de bas niveau
On écrit autant que possible dans le langage de haut niveau, puis on ne descend vers le langage de bas niveau qu’en cas de besoin
Le problème, c’est que si l’on ne maîtrise pas déjà très bien le langage de bas niveau, il faut souvent se le remettre en tête avant de pouvoir faire ce travail bas niveau
C’est pourquoi C++ ou Rust me paraissent plus difficiles à reprendre que C, et C devient donc mon choix par défaut. Mais C a aussi ses problèmes bien connus
Zig semble pouvoir très bien occuper cette bonne zone d’équilibre : assez simple pour être repris facilement après une longue pause, tout en offrant des outils modernes qui facilitent la programmation
J’essaie d’en faire autant que possible en langage de bas niveau, et je ne monte vers un langage de haut niveau que lorsque le gain de confort vaut le coût
Roc rend cela possible. Tous les programmes disposent d’une plateforme écrite dans un langage de bas niveau, et les programmes Roc utilisent l’API exposée par cette plateforme
https://www.roc-lang.org/
Bien sûr, chacun est libre de choisir son propre équilibre entre haut niveau et bas niveau
Les modèles sont implémentés en Cython, et l’API destinée aux utilisateurs est fournie en Python
Aujourd’hui je fais tout en Rust, et en particulier grâce à son système de types à la OCaml, je n’ai pas encore trouvé quelque chose que je ne puisse pas faire
Lua a été conçu comme langage embarqué, donc il est bon pour l’interopérabilité, tout en restant assez haut niveau pour être facile à utiliser
Ce n’est pas pour rien que Factorio utilise Lua comme langage de script
Ce qui me plaît dans le développement de Zig, c’est qu’ils consacrent une quantité d’efforts étonnante non pas à ajouter des fonctionnalités au langage, mais aux outils et à la boucle de feedback des développeurs
Un nouveau langage peut survivre un moment même s’il lui manque encore une fonctionnalité
Mais si compiler, lier et mettre à jour les dépendances semblent lents à chaque fois, il lui sera bien plus difficile de survivre
Cette volonté de ramener le cycle de développement non pas à l’échelle de la seconde mais à celle de la milliseconde me paraît être un bon pari sur le long terme
Je suis surpris de lire « sortie prévue de la 0.17.0 dans quelques semaines »
La 0.16 n’a-t-elle pas pris plus d’un an ?
Je ne m’attendais pas à une 0.17 aussi rapide, donc je suis très content de l’apprendre aujourd’hui
Ça semble être une bonne nouvelle. Le temps de compilation de Zig est déjà excellent, et ce changement devrait encore l’améliorer.
C’est clairement un objectif important, et les jalons pour y parvenir sont bien définis, mais en pratique il est difficile de qualifier d’« excellent » le temps d’attente pénible lors de la première compilation d’un projet vide ou quand ZLS se recompile après un
direnv allow.zig test file.zig -OReleaseSafe, cela prend plusieurs secondes sur ma machine.Et il faut attendre autant à chaque modification du fichier. Je suis sur 0.16 ou master, donc la toolchain n’est pas obsolète, et je suis sous Linux.
Le langage Zig lui-même est vraiment agréable à utiliser, mais j’ai l’impression que le compilateur et la bibliothèque standard ne sont pas développés de manière suffisamment conservatrice.
On peut ne pas remarquer ces problèmes en commençant avec un hello world, mais dès qu’on veut faire du fuzz testing ou des benchmarks, on a envie d’exécuter un binaire optimisé.
Et là, compiler même une quantité de code relativement modeste devient vraiment agaçant.
À titre de comparaison, je pense que Zig est bien meilleur comme langage que Rust/C++/C, mais ce type de problème n’arrive pratiquement jamais avec Rust/C++/C. En C/C++, je pars du principe qu’on utilise clang/gcc/ninja, etc.
Sur la même machine, avec Ninja/Python/clang, je peux configurer, compiler (-O2 ou -O3) et tester un projet C++ d’environ 10 000 lignes en 200 ms.
Ce serait vraiment bien que Zig dispose d’un mécanisme officiel pour exporter les stubs de bibliothèques Linux.
La compilation croisée de Zig et sa capacité à cibler arbitrairement des versions de glibc relèvent de la magie pure.
J’exploite cette magie dans un système de build C++ séparé, mais pour récupérer ces stubs de bibliothèques depuis Zig, il faut contourner le problème.
Ce serait bien que ce soit fourni comme sortie officielle.
Quelle raison pourrait vraiment donner envie d’utiliser ça à la place de Node.js et TypeScript ?
Si tu n’as pas besoin de la toute dernière goutte de performance ni du placement mémoire et du contrôle, Zig présente davantage d’inconvénients.
Pour du CRUD, du travail « enterprise » ou des sites web, Zig apporte très peu d’avantages.
Un programme Zig compilé peut ne faire que quelques Ko, sans dépendances.
Un accès à des tableaux écrit dans un langage bas niveau peut être optimisé avec SIMD et la parallélisation, et être plus rapide de plusieurs ordres de grandeur que le même travail en JavaScript.
La différence est importante pour le traitement de texte, la manipulation d’images, le traitement vidéo, le hachage, etc.
Les raisons de ne pas utiliser JavaScript sont en fait pratiquement infinies.