Sortie de Ruby 3.0
-
Le 25 décembre 2020 le langage Ruby est passé en version 3.0 !
Image de Jack Hunter.
Cette version est le fruit de cinq ans de travail, le travail sur la branche 3.0 ayant commencé en 2015.
La suite de cette dépêche retrace les changements contenus dans cette nouvelle version.
- lien nᵒ 1 : Site officiel de Ruby
- lien nᵒ 2 : Annonce officielle de la sortie de Ruby 3
- lien nᵒ 3 : Installer Ruby
Sommaire
- Versions
- Liens vers d’anciennes dépêches LinuxFr.org
- Performances
- Détails de fonctionnement du MJIT
- Ractor
- Fibers/coroutines
- Notre époque sera statique ou ne sera pas
- Autres nouveautés de Ruby 3.0.0
- Conclusion
Versions
La date du 25 décembre n’est pas un hasard puisque cela fait plusieurs années que les versions mineures sortent le 25 décembre.
Petit rappel des sorties de Ruby :
- Ruby 1.0 est sorti le 25 décembre 1996
- Ruby 2.0 le 24 février 2013
- Ruby 2.1 le 25 décembre 2013
- Ruby 2.2 le 25 décembre 2014
- Ruby 2.5 le 25 décembre 2015
- Ruby 2.4 le 25 décembre 2016
- Ruby 2.5 le 25 décembre 2017
- Ruby 2.6 le 25 décembre 2018
- Ruby 2.7 le 25 décembre 2019
La liste quasi complète des versions de Ruby peut se trouver ici.
Liens vers d’anciennes dépêches LinuxFr.org
Quelques liens vers des dépêches LinuxFr.org autour de Ruby et son écosystème :
- Ruby 2.0 est sorti (2013)
- JRuby 1.7.0 (2012)
- Rubinius (2010)
Performances
Une des principales améliorations de Ruby 3.0.0 concerne les performances. En effet, Ruby peut parfois exécuter votre code jusqu’à trois fois plus vite.
Ci-dessous les résultats d’un benchmark tiré de l’annonce officielle :
L’accélération provient principalement de l’utilisation du JIT soit « Just-In-Time » compilation.
Rappel, pour celles et ceux qui ne connaissent pas ce procédé : un interpréteur produit généralement du « byte code » (sorte de code simplifié de plus haut niveau que l’assembleur mais en restant bas niveau, parfois au format binaire). Ce byte code est ensuite exécuté par une « machine virtuelle ». Dans certains cas la frontière entre interpréteur et machine virtuelle est très mince (Perl, Python avec CPython, Ruby avec CRuby < 1.9) ou plus marquée (Raku = Rakudo + MoarVM/JVM/JS, Ruby = Ruby + YARV, Erlang = Erlang + BEAM) même si cela ne change pas grand-chose d’un point de vue utilisateur (le code source semble exécuté par une seule et même entité).
Le processus de génération de bytecode peut se faire parfois ligne par ligne (interpréteur de Basic) mais peut également être précédé d’une étape de génération de format intermédiaire avec des optimisations (par exemple via un arbre de syntaxe abstraite). On nomme parfois cette étape une étape de « compilation » mais il ne s’agit pas de compilation dans le même sens du terme que pour JIT.
Le JIT consiste à « compiler » certaines portions de code en code natif. Ici pour Ruby on parle de MJIT pour « Method Based JIT » c’est-à-dire que les boucles « chaudes » ne seront pas candidates à la compilation mais que seules les fonctions sont concernées.
Les mesures de performances sont présentées avec « optcarrot » qui est un émulateur de console. C’est une bonne chose puisque c’est un cas d’usage réel (les calculs mathématiques répétitifs sont parfois utilisés pour les benchmarks de JIT, mais ils sont peu représentatifs car ils leur sont très favorables). D’un autre côté, les exécutions transactionnelles (typiquement des appels web à un site web en Ruby On Rails) seront beaucoup moins sensibles aux améliorations du JIT.
Le JIT n’est pas une nouveauté de Ruby 3.0.0, il a en fait été introduit dans Ruby 2.6.
Autre bonne nouvelle, en Ruby 3.0, la taille du JIT généré a fortement été réduite !
Détails de fonctionnement du MJIT
MJIT signifie que ce sont les fonctions qui sont candidates à la compilation. Il s’agit donc de remplacer des fonctions interprétées par des fonctions compilées sur la pile d’appel de fonctions.
Le MJIT de Ruby a une approche d’observation. Les méthodes ne sont pas compilées de manière trop optimiste (ce qui semble avoir été un reproche à la JVM par le passé, qui se traduisait par un ralentissement au démarrage) mais les appels de méthodes sont notés et après plusieurs appels, une fonction est mise en attente « pour compilation ». Les approches pessimistes gâchent des gains possibles, les approches optimistes pénalisent le démarrage quant aux approches « spéculatives », elles nécessitent un mécanisme de « dé-optimisation ».
Dans la pile d’attente on retrouve les fonctions à optimiser, attendant leur tour pour être compilées. L’exécution continue donc en parallèle et lorsque la fonction est compilée par ce processus externe, il peut remplacer la version interprétée sur la pile d’appel.
La compilation se fait en utilisant un compilateur disponible sur la plateforme. Tout simplement en générant un code source en C, puis en utilisant GCC pour compiler:
Image tirée de The method JIT compiler for Ruby 2.6 par k0kubun.
L’avantage de ce processus est sa simplicité et de pouvoir « s’acheter » facilement de la portabilité (JIT partout ou GCC est disponible) et également de bénéficier de l’optimisation de code des compilateurs.
Mais cela pose tout de même certains problèmes :
- accès en écriture nécessaire pour Ruby sur une portion du système hôte ;
- nécessité de disposer des outils de développement sur la machine hôte (vous avez GCC sur vos machines de production ? Pas moi !)
Ractor
Ruby 3.0.0 introduit également les Ractor. Il s’agit d’un modèle de concurrence basé sur les messages.
Les ractors permettent de gérer plus facilement les exécutions concurrentes tout en aidant à l’isolation des données. Les ractors sont une abstraction de concurrence et ils se veulent thread safe (partagent moins que des threads) sans pour autant le garantir. Chaque ractor possède un ou plusieurs fils d’exécution.
Les Ractor sont expérimentaux et n’acceptent pas toute la syntaxe Ruby.
Fibers/coroutines
Ruby 3.0 introduit également de nouvelles méthodes pour la concurrence légère avec des nouvelles routines de contrôle.
Notre époque sera statique ou ne sera pas
Depuis de nombreuses années, les langages interprétés à typage faible proposent des annotations pour donner les types de manière « statique ».
RBS
Ruby 3.0 étend sa syntaxe grâce à RBS qui est une gem qui apporte une grammaire de définitions.
RBS permet d’ajouter la syntaxe pour des types avancés comme les unions, la surcharge de méthodes, les génériques ou le duck typing avec interfaces.Une définition RBS pourra ressembler à ceci :
# Classes class User attr_reader name : String attr_reader age : Integer def initialize : (name: String, age: Integer) -> [String, Integer] end
TypeProf
TypeProf est un analyseur de programme qui permet de générer (déduire) les annotations (la grammaire RBS vue précédemment) à partir d’un programme Ruby.
TypeProf n’est pas encore compatible avec toute la syntaxe Ruby.À terme, on pourra utiliser le source Ruby avec ses annotations pour faire tout ce qu’on fait habituellement avec de l’analyse statique de code.
Autres nouveautés de Ruby 3.0.0
L’assignement avec la flèche
=>
par exemple42 => linuxfr
.Nouveau comportement de
in
:in
retourne à présent un booléen ;- pour trouver un pattern ;
case/in
perd son statut « expérimental ».
Nouvelle syntaxe : des méthodes « sans fin » (sans mot clé
end
).Conclusion
Ruby sort donc sa version majeure qui se concentre sur les performances, l’exécution concurrente et le typage statique.
Souhaitons bonne route à la branche 3 de Ruby tout en rappelant que le site sur lequel vous lisez cette dépêche est lui-même écrit en… Ruby !
Télécharger ce contenu au format EPUB
Commentaires : voir le flux Atom ouvrir dans le navigateur
Sauf mention contraire, le site est placé sous double licence Creative Commons BY-SA et GNU Free Documentation License propulsé par NodeBB