Ruby 3.0.0 est disponible

Nous avons le plaisir de vous annoncer la sortie de Ruby 3.0.0. Nous avons travaillé dur pour atteindre Ruby 3 dont les objectifs sont la performance, la concurrence et le typage. Surtout en ce qui concerne les performances, Matz a déclaré que “Ruby3 sera trois fois plus rapide que Ruby2” a.k.a. Ruby 3x3.

Optcarrot 3000 frames

Avec Optcarrot benchmark qui mesure les performances d’un seul thread basé sur la charge de travail de l’émulation d’un jeu NES, Ruby 3.0 a atteint des performances trois fois plus rapide que Ruby 2.0 !

Les mesures ont été réalisées dans l’environnement suivant : benchmark-driver.github.io/hardware.html. Commit 8c510e4095 a été utilisé comme Ruby 3.0. Cela peut ne pas être trois fois plus rapide en fonction de votre environnement ou benchmark.

Ruby 3.0.0 couvre les objectifs de :

  • Performance
    • MJIT
  • Concurrence
    • Ractor
    • Fiber Scheduler
  • Typage (Analyse statique)
    • RBS
    • TypeProf

Avec le gain de performance, Ruby 3.0 amène de nouvelles fonctionnalités (décrites ci-dessous).

Performance

Quand j’ai déclaré “Ruby3x3” pour la première fois dans le discours d’ouverture de la conférence, de nombreux membres de la core team ont estimé que “Matz est un vantard”. En fait, j’en avais aussi l’impression. Mais nous l’avons fait. Je suis honoré de voir ce que la core team a réellement accomplie pour rendre Ruby3.0 trois fois plus rapide que Ruby2.0 (dans certains benchmarks). – Matz

MJIT

Plusieurs améliorations ont été implémentées dans MJIT. Voir News pour plus de détails.

A partir de Ruby 3.0, JIT est censé améliorer les performances des charges de travail limitées telles que les jeux (Optcarrot), l’IA (Rubykon), ou n’importe quelle application qui passe la majorité de son temps à appeler quelques méthodes de nombreuse fois.

Bien que Ruby 3.0 ait considérablement réduit la taille du code JIT, il n’est toujours pas prêt pour optimiser des charges de travail comme Rails, qui passe souvent du temps sur tant de méthodes qu’il souffre de problèmes d’i-cache exacerbés par JIT. Restez à l’écoute de Ruby 3.1 pour d’autres améliorations sur ce problème.

Concurrence / Parallèle

Aujourd’hui est l’ère du multi-cœur. La concurrence est très importante. Avec Ractor et Async Fiber, Ruby sera un véritable langage concurrent. — Matz

Ractor (expérimental)

Ractor est un modèle d’acteur permettant une abstraction pour la concurrence. Il fournit un outil permettant l’exécution de code de façon thread-safe.

Vous pouvez créer plusieurs ractors et les lancer en parallèle. Ractor vous permet de créer des programmes thread-safe puisque les ractors ne partagent pas d’objets normaux. La communication entre ractors se fait par passage de messages.

Afin de limiter le partage d’objet, Ractor introduit plusieurs restrictions sur la syntaxe de Ruby (sans plusieurs ractors, il n’y a pas de restriction).

La spécification et l’implémentation ne sont pas matures et pourront donc changer. Cette fonctionnalité est marquée comme expérimentale et montre l’avertissement “experimental feature” au premier Ractor.new.

Le bout de code suivant mesure le temps d’exécution du célèbre benchmark de (la fonction de Takeuchi - Wikipedia) en l’exécutant quatre fois de façon séquentielle ou quatre fois en parallèle avec des ractors.

def tarai(x, y, z) =
  x <= y ? y : tarai(tarai(x-1, y, z),
                     tarai(y-1, z, x),
                     tarai(z-1, x, y))
require 'benchmark'
Benchmark.bm do |x|
  # sequential version
  x.report('seq'){ 4.times{ tarai(14, 7, 0) } }

  # parallel version
  x.report('par'){
    4.times.map do
      Ractor.new { tarai(14, 7, 0) }
    end.each(&:take)
  }
end
Benchmark result:
          user     system      total        real
seq  64.560736   0.001101  64.561837 ( 64.562194)
par  66.422010   0.015999  66.438009 ( 16.685797)

Les résultats sont mesurés sur Ubuntu 20.04, Intel(R) Core(TM) i7-6700 (4 cores, 8 hardware threads). Cela montre que la version exécutée en parallèle est 3.87 fois plus rapide que la version séquentielle.

Voir doc/ractor.md pour plus de détails.

Fiber Scheduler

Fiber#scheduler est introduit pour intercepter des opérations de blocage. Cela permet une concurrence légère sans changer le code existant. Voir “Don’t Wait For Me, Scalable Concurrency for Ruby 3” pour avoir un aperçu du fonctionnement.

Les classes et méthodes prises en charge :

  • Mutex#lock, Mutex#unlock, Mutex#sleep
  • ConditionVariable#wait
  • Queue#pop, SizedQueue#push
  • Thread#join
  • Kernel#sleep
  • Process.wait
  • IO#wait, IO#read, IO#write et les méthodes rattachées (e.g. #wait_readable, #gets, #puts etc.)
  • IO#select n’est pas prise en charge.

Cet exemple de code permet de faire plusieurs requêtes HTTP de façon concurrente.

require 'async'
require 'net/http'
require 'uri'

Async do
  ["ruby", "rails", "async"].each do |topic|
    Async do
      Net::HTTP.get(URI "https://www.google.com/search?q=#{topic}")
    end
  end
end

Cela utilise async qui fournit la boucle d’évènements. Cette boucle utilise les hooks Fiber#scheduler pour rendre Net::HTTP non bloquant. D’autres gemmes peuvent utiliser cette interface afin de fournir une exécution non bloquante à Ruby et peuvent être compatibles avec d’autres implémentations de Ruby (par exemple, JRuby, TruffleRuby) qui peuvent prendre en charge les mêmes hooks non bloquants.

Analyse statique

Les années 2010 ont été une ère de langages de programmation statiquement typés. Ruby souhaite un futur avec de la vérification de type statique, sans déclaration de type, en utilisant une interprétation abstraite. RBS et TypeProf sont la première étape vers le futur. D’autres étapes sont à venir. — Matz

RBS

RBS est un langage qui décrit les types de programmes Ruby.

Les vérificateurs de type, y compris TypeProf et d’autres outils prenant en charge RBS, comprendront mieux les programmes Ruby avec des définitions RBS.

Vous pouvez écrire la définition des classes et des modules: les méthodes qui sont définies dans la classe, les variables d’instance et leurs types, et les relations d’héritage / mix-in.

Le but de RBS est de prendre en charge les modèles couramment observés dans les programmes Ruby et de vous permettre d’écrire des types avancés, notamment les unions de type, les surcharges de méthode et les génériques. Il prend également en charge le duck typing avec interface types.

Ruby 3.0 arrive avec la gemme rbs, qui inclue l’analyse et le traitement des définitions de type écrites en RBS.

Le code ci-dessous est un petit exemple de RBS une classe, un module et des définitions de constantes.

module ChatApp
  VERSION: String
  class Channel
    attr_reader name: String
    attr_reader messages: Array[Message]
    attr_reader users: Array[User | Bot]              # `|` means union types, `User` or `Bot`.
    def initialize: (String) -> void
    def post: (String, from: User | Bot) -> Message   # Method overloading is supported.
            | (File, from: User | Bot) -> Message
  end
end

Voir le README de la gemme rbs pour plus de détails.

TypeProf

TypeProf est un outil d’analyse de type inclus dans Ruby.

Actuellement, TypeProf permet une sorte d’inférence de type.

Il lit du code Ruby sans annotation de type, analyse quelles méthodes sont définies, comment elles sont utilisées et génère un prototype de la signature de type au format RBS.

Voici une simple démo de TypeProf.

Un exemple d’entrée :

# test.rb
class User
  def initialize(name:, age:)
    @name, @age = name, age
  end
  attr_reader :name, :age
end
User.new(name: "John", age: 20)

Un exemple de sortie :

$ typeprof test.rb
# Classes
class User
  attr_reader name : String
  attr_reader age : Integer
  def initialize : (name: String, age: Integer) -> [String, Integer]
end

Vous pouvez lancer TypeProf en sauvegadant le code dans un fichier “test.rb” et en appelant la commande “typeprof test.rb”.

Vous pouvez aussi essayer TypeProf en ligne. (Cela lance TypeProf coté serveur, donc désolé si cela ne fonctionne pas !)

Voir la documentation et les démos pour plus de détails.

TypeProf est expérimental et n’est pas encore mature. Seulement un sous ensemble du langage Ruby est supporté et la détection des erreurs de typage est limitée. Mais il continue de croître rapidement pour améliorer la couverture des fonctionnalités du langage, les performances d’analyse et la convivialité. Tout commentaire est le bienvenu.

Autres ajouts notables

  • Le filtrage par motif en une ligne est changé (expérimental).

    • => est ajouté. Il peut être utilisé comme une affectation à droite.

      0 => a
      p a #=> 0
      
      {b: 0, c: 1} => {b:}
      p b #=> 0
      
    • in est changé pour retourner true ou false.

      # version 3.0
      0 in 1 #=> false
      
      # version 2.7
      0 in 1 #=> raise NoMatchingPatternError
      
  • Le pattern Find est ajouté (expérimental).

    case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
    in [*pre, String => x, String => y, *post]
      p pre  #=> ["a", 1]
      p x    #=> "b"
      p y    #=> "c"
      p post #=> [2, "d", "e", "f", 3]
    end
    
  • La définition de méthode sans le mot clé end est ajoutée.

    def square(x) = x * x
    
  • Hash#except est désormais inclus.

    h = { a: 1, b: 2, c: 3 }
    p h.except(:a) #=> {:b=>2, :c=>3}
    
  • Memory view est ajoutée en tant que fonctionnalité expérimentale

    • C’est un nouvel ensemble d’API C pour échanger une zone mémoire brute, comme un tableau de nombre ou une image bitmap, entre des bibliothèques d’extension. Les bibliothèques d’extension peuvent également partager les méta données de la zone mémoire comprenant la forme, le format de l’élément, etc. En utilisant ce type de métadonnées, les librairies d’extension peuvent même partager des tableaux multidimensionnels de façon appropriée. Cette fonctionnalité a été conçue en utilisant le protocole tampon de python.

Amélioration des performances

  • Coller du code dans IRB est 53 fois plus rapide qu’en Ruby 2.7.0. Par exemple, le temps nécessaire pour coller cet exemple de code passe de 11.7 secondes à 0.22 secondes.
  • La commande measure est ajoutée à IRB. Elle permet simplement de mesurer le temps.

    irb(main):001:0> 3
    => 3
    irb(main):002:0> measure
    TIME is added.
    => nil
    irb(main):003:0> 3
    processing time: 0.000058s
    => 3
    irb(main):004:0> measure :off
    => nil
    irb(main):005:0> 3
    => 3
    

Autres changements notables depuis la version 2.7

  • Les arguments de mot-clé sont séparés des autres arguments.

    • En principe, le code qui affiche un avertissement dans la version 2.7 de Ruby ne fonctionnera pas. Voir le document pour plus de détails.
    • La transmission d’arguments prend désormais en charge les arguments principaux.

      def method_missing(meth, ...)
        send(:"do_#{ meth }", ...)
      end
      
  • Le filtrage par motif (case/in) n’est plus expérimentale.
  • La fonctionnalité $SAFE a été completement supprimée. C’est désormais une variable globale.
  • L’ordre de la backtrace a été inversé depuis la version 2.5 de Ruby, mais cela a été remis comme avant. Cela se comporte comme la version 2.4 de Ruby. Le message d’erreur et le numéro de ligne où l’exception apparait sont affichés en premiers. Les appelants sont affichés après.
  • Plusieurs bibliothèques standard ont été mises à jour.
    • RubyGems 3.2.3
    • Bundler 2.2.3
    • IRB 1.3.0
    • Reline 0.2.0
    • Psych 3.3.0
    • JSON 2.5.1
    • BigDecimal 3.0.0
    • CSV 3.1.9
    • Date 3.1.0
    • Digest 3.0.0
    • Fiddle 1.0.6
    • StringIO 3.0.0
    • StringScanner 3.0.0
    • etc.
  • Les librairies suivantes ne sont plus incluses. Il faut installer les gemmes correspondantes pour utiliser leurs fonctionnalitées.
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • Les gemmes suivantes sont désormais incluses avec Ruby.
    • rexml
    • rss
  • Les fichiers stdlib suivants sont désormais des gemmes et sont disponibles sur rubygems.org.
    • English
    • abbrev
    • base64
    • drb
    • debug
    • erb
    • find
    • net-ftp
    • net-http
    • net-imap
    • net-protocol
    • open-uri
    • optparse
    • pp
    • prettyprint
    • resolv-replace
    • resolv
    • rinda
    • set
    • securerandom
    • shellwords
    • tempfile
    • tmpdir
    • time
    • tsort
    • un
    • weakref
    • digest
    • io-nonblock
    • io-wait
    • nkf
    • pathname
    • syslog
    • win32ole

Voir NEWS ou les logs de commit pour plus de détails.

Avec ces changements, 4028 fichiers changés, 200058 insertions(+), 154063 suppressions(-) depuis Ruby 2.7.0!

Ruby3.0 est une étape importante. Le langage évolue en restant compatible. Mais ce n’est pas la fin. Ruby continuera à progresser et deviendra encore plus grand. Restez à l’écoute ! — Matz

Joyeux Noël, bonnes vacances, et profitez de la programmation avec Ruby 3.0!

Téléchargement

  • https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.tar.gz

    SIZE: 19539509
    SHA1: 233873708c1ce9fdc295e0ef1c25e64f9b98b062
    SHA256: a13ed141a1c18eb967aac1e33f4d6ad5f21be1ac543c344e0d6feeee54af8e28
    SHA512: e62f4f63dc12cff424e8a09adc06477e1fa1ee2a9b2b6e28ca22fd52a211e8b8891c0045d47935014a83f2df2d6fc7c8a4fd87f01e63c585afc5ef753e1dd1c1
    
  • https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.tar.xz

    SIZE: 14374176
    SHA1: c142899d70a1326c5a71311b17168f98c15e5d89
    SHA256: 68bfaeef027b6ccd0032504a68ae69721a70e97d921ff328c0c8836c798f6cb1
    SHA512: 2a23c2894e62e24bb20cec6b2a016b66d7df05083668726b6f70af8338211cfec417aa3624290d1f5ccd130f65ee7b52b5db7d428abc4a9460459c9a5dd1a450
    
  • https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.zip

    SIZE: 23862057
    SHA1: 2a9629102d71c7fe7f31a8c91f64e570a40d093c
    SHA256: a5e4fa7dc5434a7259e9a29527eeea2c99eeb5e82708f66bb07731233bc860f4
    SHA512: e5bf742309d79f05ec1bd1861106f4b103e4819ca2b92a826423ff451465b49573a917cb893d43a98852435966323e2820a4b9f9377f36cf771b8c658f80fa5b
    

Ruby, c’est quoi ?

Ruby a été initialement développé par Matz (Yukihiro Matsumoto) en 1993 puis est devenu open source. Il fonctionne sur de nombreuses plates-formes et est utilisé partout dans le monde, en particulier pour le développement web.