Le projet Ruby Tumbler avance étonnamment bien. Je suis allée plus loin que prévu et j’ai appris des trucs fascinants sur les langages dynamiques.
Ça avance aussi plus lentement que prévu.
J’ai dû être plus méthodique dans mes expériences et réfléchir sérieusement à ce que devrait être un compilateur Ruby natif efficace.
Préserver le comportement dynamique
Parce que le problème n’est pas simplement de traduire du Ruby en code machine.
Le Ruby est extraordinairement dynamique. On peut remplacer des méthodes au runtime. Les objets peuvent changer de comportement. Les closures peuvent survivre à leurs créateurs. Et les programmeurs s’attendent à ce que tout continue de fonctionner.
L’astuce, c’est de préserver ces comportements sans payer le prix de toute la machinerie en permanence.
Ça m’a menée à l’une de mes expériences préférées : une gestion de la mémoire inspirée de Rust, sans demander aux devs Ruby de se prendre la tête avec des scénarios de ownership.
Le compilateur peut prouver quand un bloc ne s’échappera pas, emprunter son stockage capturé et éviter d’allouer une closure sur le tas. Au runtime, il vérifie la méthode qui va réellement s’exécuter.
Si cette implémentation n’est pas prouvée sûre, on retombe sur le comportement classique géré par le tas.
Pas d’annotations de ownership, pas de déclarations de lifetime, pas besoin de réécrire votre app.
Dans des tests contrôlés, ce processus a éliminé deux allocations par appel et tous les garbage collections dans les lots chronométrés. Je suis vraiment contente de la façon dont ça s’assemble.
Je travaille toujours sur la compatibilité avec CRuby. Certaines fonctionnalités dynamiques demandent encore du boulot, et le backend expérimental d’objets natifs ne boot pas encore Rails complètement. Je teste avec une demi-douzaine de repos Rails choisis au hasard, de tailles et complexités différentes, parce que je préfère débusquer les cas limites tordus maintenant.
Mon but est d’éviter de forcer les devs à faire des refactorisations majeures juste pour profiter de la compilation native.
Pendant ce temps, sous le capot, toute la machine a changé.
Des catégories entières de travail au runtime ont été consolidées, spécialisées ou supprimées.
Et je me rends compte que la question importante n’est pas toujours de savoir comment rendre les opérations Ruby individuelles plus rapides, mais plutôt comment éviter d’effectuer des opérations inutiles dès le départ.
Pour l’instant, je priorise la performance au runtime et la gestion de la mémoire plutôt que la vitesse de compilation.
Ce que montrent les benchmarks
C’est combien de fois plus rapide ?
Mes benchmarks internes montrent toujours environ 3× CRuby sur les charges de travail que j’ai explorées.
C’est loin d’être définitif. Certaines opérations s’améliorent beaucoup plus. D’autres moins. Je ne peux pas encore promettre un gain de vitesse pour une classe particulière d’application Rails dans toutes les situations... pour l’instant.
Mais il se passe quelque chose d’intéressant.
Ce qui n’était au début qu’une collection d’expériences a maintenant une philosophie cohérente autour du dispatch dynamique, du ownership mémoire et de la préservation de la sémantique du langage sans overhead inutile.
Ça commence à ressembler moins à un tas d’expériences et plus à un vrai produit cohérent.
Selon mon planning, j’aurai peut-être besoin de deux semaines de plus avant d’être prête à publier mon repo.
Ça m’excite.
Hâte de partager ça.
Je vous aime tous.