RAII, le fantasme de Rust/Linux
(kristoff.it)Résumé
C’est un texte écrit après avoir observé le conflit entre les développeurs Rust et les développeurs Linux historiques. Différents développeurs peuvent certes avoir chacun leur propre style de code, mais le projet Linux a déjà écarté C++ par le passé pour éviter son style de code et sa structure (RAII).
Le fonctionnement du code mentionné par Asahi Lina est beaucoup trop lent à l’arrêt du programme, et va à l’encontre du traitement par lots, qui est l’approche la plus fondamentale pour créer des logiciels orientés performance. Par exemple, effectuer un traitement par lots à l’aide de régions mémoire permet d’aligner plusieurs durées de vie en une seule, ce qui rend RAII inutile.
Voici des ressources à l’appui de mon argumentation. Elles expliquent toutes pourquoi le traitement par lots est bénéfique :
- 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
Je pense donc que Linux ne devrait jamais adopter RAII.
Si j’ai apporté ce texte ici, c’est parce que j’ai vu à plusieurs reprises des développeurs Rust coréens se montrer très en colère en le lisant, et je me demandais donc ce que les gens ici en penseraient. Qu’en pensez-vous ?
11 commentaires
C’est mon point de vue, mais je comprends dans une certaine mesure l’élitisme de certains développeurs. Du point de vue de l’« ingénierie » logicielle, il est difficile de trouver un « logiciel » qui, comme Linux, a aujourd’hui aidé les progrès de la philosophie open source en collaborant largement, y compris avec le camp du closed source, dans l’écosystème open source. Dès lors, n’adoptent-ils pas une attitude conservatrice, encore plus exclusive et paraissant même luddiste, par crainte que des programmeurs non éprouvés, portés par Rust, n’affluent comme une marée, ajoutent de manière désordonnée du code échappant au contrôle du noyau central des mainteneurs des projets existants, fassent exploser la dette technique et raccourcissent ainsi le cycle de vie de Linux ?
Il est intéressant de voir qu’on adopte une attitude peu « ouverte » pour que l’open source puisse rester longtemps de l’open source.
Moi aussi, j’utilise souvent et recommande des formes de gestion des ressources comme le RAII. Même quand on ne sait pas vraiment ce qu’est le RAII et qu’on l’utilise machinalement, on obtient malgré tout du code « au moins sûr ».
En revanche, si on ne l’utilise pas en comprenant bien son fonctionnement, on risque facilement de produire en masse du code inefficace, du genre à ouvrir et fermer des fichiers des dizaines de fois alors qu’une seule ouverture suffirait. Si les développeurs gardent en permanence un œil sur les performances et que cette culture est bien ancrée dans l’équipe, je pense qu’on peut obtenir des performances tout à fait suffisantes même avec le RAII.
freeà chaque destruction d’objetfree, puis l’exécuter en bulk ?Sous Linux, existe-t-il une fonctionnalité ? Une API, par exemple, qui permette à 2 de s’exécuter plus vite que 1 ?
J’ai toujours naturellement vécu avec 1, donc j’ai du mal à bien comprendre.
Je n’ai pas envie de revenir à une expérience de développement où, une fois tout le code terminé, on cherche les fuites de mémoire avec valgrind.
Je ne le sais pas exactement, mais refuser d’utiliser le RAII semble vouloir dire qu’on compte améliorer les performances de fermeture en s’appuyant sur des fuites mémoire intentionnelles ; je ne sais pas si c’est vraiment la bonne direction.
De toute façon, un développeur capable de bien gérer la mémoire manuellement saura aussi bien utiliser le RAII, et un développeur incapable de développer sans RAII ne saura pas non plus gérer la mémoire manuellement ; je ne vois donc pas de raison de ne pas utiliser le RAII.
Je me demandais combien de temps
freeconsomme, alors j’ai écrit un petit test — certes très différent d’une charge réelle. (Compilé en release Rust, avecstd::alloc::allocetstd::fs::File.)J’ai alloué 10 000 000 blocs mémoire de tailles variées, soit environ 2,5 Go au total, puis j’ai mesuré uniquement le temps de libération : cela a pris 1,87 seconde. Soit 187 ns par bloc.
À l’inverse, pour les fichiers, j’ai ouvert seulement environ 10 000 handles, puis mesuré uniquement le temps nécessaire pour les fermer : cela a pris environ 9 secondes. Cela revient à 900 µs par fichier.
(Ce PC Windows est particulièrement lent sur les opérations de fichiers, peut-être à cause de l’antivirus. Sur un autre portable Windows, j’obtenais respectivement 400 ns / 200 µs, et sur un autre PC Linux 50 ns / 600 ns.)
Comme alternatives au RAII, on évoque souvent le traitement en masse, ou encore le fait de faire confiance à l’OS à la fin du processus et de laisser fuiter les ressources. Pour la mémoire, cela semble facile à faire.
En revanche, pour des ressources comme les fichiers ou les sockets, je n’ai jamais vu d’API de récupération en masse, et si on laisse fuiter les ressources, même si le temps côté code utilisateur diminue, le noyau récupère exactement ce coût au moment de terminer le processus. Le gain de performance est donc faible.
Le RAII pour la mémoire n’est pas si lent que ça, n’empêche pas non plus d’utiliser des arenas, et n’interdit pas non plus les fuites intentionnelles si besoin ; il me semble donc difficile d’en faire une raison d’éviter le RAII.
Et pour le RAII sur les fichiers, qui est plus lent, dans une situation où il n’existe ni moyen de traiter cela en masse ni moyen d’éviter ce coût, je me demande à quel point les alternatives au RAII sont réellement meilleures.
C’est un peu à côté du sujet, mais j’ai l’impression que les objections au RAII et aux lifetimes se limitent souvent aux seules ressources mémoire, représentées par
malloc/free.Or RAII et les lifetimes sont utiles bien au-delà de l’allocation mémoire : non seulement pour les ressources OS comme les fichiers, sockets ou verrous, mais aussi pour des modèles de ressources comme les object pools ou les connection pools, bref pour la plupart des ressources qui impliquent acquisition et restitution, avec contrôle d’accès exclusif pendant la période de possession.
Ces ressources partagent elles aussi une structure comparable à
malloc/free, et partagent donc les mêmes familles de problèmes : fuites, use-after-free, double free, etc.Et justement parce qu’elles partagent cette même structure, RAII et les lifetimes ne résolvent pas seulement les problèmes mémoire, mais aussi ceux de toutes ces autres ressources ; je pense que cet aspect mériterait d’être davantage mis en avant.
Par exemple, en Rust, on empêche aussi à la compilation les use-after-close et double close sur les handles de fichiers :
https://play.rust-lang.org/?version=stable&mode=debug&edition=…
Les principaux langages à GC gèrent bien la mémoire via le GC, mais pour des ressources comme les file handles ou les sockets, dont la gestion doit rester déterministe, ils finissent malgré tout par introduire en plus des mécanismes de type RAII (comme
try-with-resourcesen Java,usingen C#, ouwithen Python) ou des mécanismes proches (commedeferen Go).On se retrouve donc avec plusieurs modes de gestion des ressources dans un même langage, et je me dis que ce n’est peut-être pas aussi bon que cette approche.
Si vous voulez parler d’une arène, Rust dispose bien sûr aussi d’arènes, et il est également possible, grâce aux lifetimes, d’interdire l’accès aux éléments de l’arène après sa « libération en bloc » résultant de la disparition de l’arène. Veuillez consulter https://crates.io/keywords/arena.
J’aimerais qu’il y ait beaucoup d’autres langages, même après Zig et Rust. Mais jusqu’à présent, je n’ai pas encore vu de langage aussi pertinent que Rust. Je pense plutôt que les connaissances entre développeurs qui émergent de ces discussions entre langages sont utiles. Haha..
Je suis moi aussi développeur, et j’utilise Rust comme langage principal à ma façon ; cela ne m’a pas mis en colère, mais j’ai tout de même eu l’impression qu’on allait chercher un exemple un peu extrême (en disant que « le programme est trop lent à se fermer », et même dans la vidéo liée, il s’agit d’un cas sans lien direct avec un projet Rust : la fermeture de Visual Studio prend trop de temps parce que les destructeurs de chaque composant individuel sont appelés).
Lorsqu’il est nécessaire, pour des raisons de performance, de traiter en une seule fois le nettoyage de plusieurs composants, on peut sans doute choisir non pas d’implémenter
Droppour chaque composant individuellement, mais d’implémenterDropsur le type qui porte la durée de vie de ces composants afin d’effectuer le nettoyage en bloc. Ce serait encore mieux avec une protection empêchant de créer ces composants autrement que via l’API de ce type.Bien sûr, la crainte de l’auteur de ce texte semble être que si la pratique consistant à utiliser la RAII entre dans la base de code de Linux, du code présentant des inquiétudes de performance très implicites s’accumule à long terme au sein d’une base de code immense et complexe, au point de provoquer un jour quelque chose de similaire à Visual Studio ; je pense que c’est effectivement une inquiétude tout à fait légitime. Cela dit, comme cela a été mentionné dans d’autres commentaires, la RAII apporte aussi de la sûreté ; à mon avis, le choix relève donc en partie d’un compromis.
Les deux côtés disent des choses justes.
Pour prendre une analogie, dans le jeu en ligne LoL, le personnage Azir est perçu comme un champion de très haut tier, avec un split push, un contrôle de zone en teamfight et une valeur d’ultime écrasants, mais cela ne vaut que dans des matchs professionnels à très haut niveau de maîtrise ; au niveau du joueur moyen, sa phase de lane est bien trop faible et son power level général aussi, si bien qu’il n’est qu’un champion de tout dernier tier.
Du point de vue de personnes comme Asahi Lina, qui font partie des 10 % supérieurs en connaissances de programmation et de systèmes d’exploitation, des alternatives à RAII seront évidemment préférables, mais sur le terrain que manipulent les 90 % restants, je pense qu’il n’y a rien de mieux que RAII ou Rust.
Cela dit, comme l’une des grandes raisons d’exiger la memory safety / sûreté mémoire est la sécurité... je pense que ce trade-off est inévitable.
Sans RAII, j’ai l’impression que des développeurs relativement moins expérimentés risquent de produire des bugs à la chaîne
au niveau des applications, et pas de l’OS, au moins...