2 points par GN⁺ 2024-12-06 | 1 commentaires | Partager sur WhatsApp

Améliorer les performances de Ruby : réécrire du C en Ruby

Comparaison des performances de Ruby
  • Dans un dépôt récent de comparaison de langages, Ruby est plus rapide que R et Python, mais est classé comme le troisième langage le plus lent.
  • Les benchmarks se composent de deux tests, « Loops » et « Fibonacci », qui mettent respectivement en avant les boucles et les conditions, ainsi que le coût des appels de fonction et les performances de la récursion.
Comparaison des performances entre Ruby et Node.js
  • Sur un MacBook Pro M3, Ruby 3.3.6 prend 28 secondes pour l’exemple de boucle et 12 secondes pour l’exemple Fibonacci.
  • Node.js prend environ 1 seconde pour chacun des deux exemples.
  • Sur un MacBook Air M2, les performances de Ruby sont encore moins bonnes.
Signification des benchmarks
  • Ces benchmarks n’ont peut-être pas une grande signification en pratique.
  • Python a été classé comme le langage le plus lent, mais c’est pourtant le langage le plus utilisé sur GitHub.
  • Un langage de programmation doit être efficace, mais son utilité et la productivité qu’il apporte sont plus importantes que ses performances.
Application de YJIT
  • L’activation de YJIT améliore fortement les performances de Fibonacci.
  • Sur l’exemple de boucle, le gain de performances reste minime.
Optimisation du code Ruby
  • Range#each est écrit en C, ce qui empêche toute optimisation par YJIT.
  • Integer#times a été converti du C vers Ruby dans Ruby 3.3, ce qui permet son optimisation par YJIT.
  • Array#each a été converti du C vers Ruby dans Ruby 3.4.
Optimisation de Integer#times
  • Integer#succ fonctionne plus rapidement que i += 1.
  • YJIT optimise Integer#times, ce qui améliore fortement les performances.
Optimisation de Array#each
  • Array#each a été converti du C vers Ruby dans Ruby 3.4, ce qui permet son optimisation par YJIT.
  • Le module Primitive est utilisé pour évaluer du code C depuis Ruby.
Dépôt Ruby Microbench
  • Les benchmarks sont exécutés avec différentes versions de Ruby et avec YJIT.
  • Ruby 3.4 avec YJIT montre une nette amélioration des performances.
Optimisation de range#each
  • Il est possible d’améliorer les performances en implémentant la classe Range en Ruby pur.
Bibliothèque standard YJIT
  • L’équipe YJIT améliore les performances en remplaçant du code C par du Ruby.
  • Le bloc with_yjit permet d’utiliser l’implémentation Ruby lorsque YJIT est activé.
Analyse des optimisations de YJIT
  • YJIT optimise les performances en convertissant le bytecode de la VM Ruby en code machine.
  • L’analyse du code machine de Integer#succ permet de comprendre le processus d’optimisation de YJIT.

1 commentaires

 
GN⁺ 2024-12-06
Commentaires Hacker News
  • L’exemple de boucle répète l’opération un milliard de fois et utilise des boucles imbriquées. On suppose que ce benchmark passera plus de 99 % de son temps sur les deux premières lignes

    • Grâce à une analyse de vivacité sur les éléments du tableau, il serait possible d’éliminer toute la boucle externe et de transformer simplement le programme
    • Je me demande si le compilateur peut effectuer ce type d’analyse
    • Même si u n’est pas connu à la compilation, la boucle interne pourrait être remplacée par quelques instructions
  • Il est fait mention des futures versions de Ruby : Ruby 3.4.0 devrait sortir à Noël cette année, et Ruby 3.5.0 au Noël suivant

    • Je me demande quel effet le JIT minimal de Python aurait sur ce type de boucles
    • Python 3.13 doit être compilé avec le JIT activé, et il serait intéressant d’exécuter ce benchmark dans ces conditions
  • J’ai toujours de l’affection pour Ruby. Merci à Matz

  • Il y a eu un PR d’amélioration des performances de Integer#succ au début de 2024, ce qui m’a permis de comprendre pourquoi on utilise Integer#succ

    • Integer#succ est utilisé lors de la réécriture de méthodes de boucle, et dans l’interpréteur opt_succ (i = i.succ) est traité plus rapidement que putobject 1; opt_plus (i += 1)
    • Personnellement, j’utilise souvent #succ pour la lisibilité, et je l’emploie deux fois dans la méthode #bytes d’une bibliothèque UUID pour conserver un « mode bit slicing » lors de la lecture du code
  • Partage d’une expérience liée à TruffleRuby, qui serait plus rapide que Node.js et proche de Bun ou de Golang

    • Je ne suis pas certain que le benchmark fourni montre les performances de TruffleRuby après les modifications
    • J’aimerais vérifier le benchmark et l’ajouter au dépôt principal sous forme de commit
  • Ruby est devenu très rapide, et TruffleRuby est encore plus impressionnant

  • Je ne savais pas que YJIT était écrit en Rust

  • Python était le langage le plus lent dans le benchmark, mais en octobre 2024 c’était aussi le langage le plus utilisé sur GitHub

    • Il semble y avoir une corrélation entre la lenteur d’un langage et sa popularité
  • Il existe un ancien dépôt de comparaison de langages incluant davantage de langages

  • Cela a profondément changé les solutions Advent of Code, et cela paraît étonnamment similaire