Publicado Ruby 3.0.0

Nos complace anunciar la publicación de Ruby 3.0.0. Desde el 2015 desarrollamos Ruby 3 con esfuerzo y con metas en desempeño, concurrencia y tipado. Especialmente en desempeño, Matz estableció “Ruby3 será 3 veces más rápido que Ruby2”, afirmación también conocida como Ruby 3x3.

Optcarrot 3000 frames

Con el punto de referencia Optcarrot, que mide desempeño con un solo hilo de ejecución con la carga de emular juegos para NES, ¡logra un desempeño 3 veces más rápido que Ruby 2.0!

Se midió en el ambiente anotado en benchmark-driver.github.io/hardware.html. La contribución 8c510e4095 se usó como Ruby 3.0. Podría no resultar 3 veces más rápido en de su ambiente o con otros punto de referencia.

Ruby 3.0.0 cubre esas metas asi

  • Desempeño
    • MJIT
  • Concurrencia
    • Ractor
    • Planificador de fibras (Fiber Scheduler)
  • Tipado (Análisis Estático)
    • RBS
    • TypeProf

Con la mejora en desempeño mencionada, Ruby 3.0 introduce diversas características nuevas que se describen a continuación.

Desempeño

Cuando mencioné “Ruby3x3” en la charla principal de una conferencia, muchos, incluso miembros del equipo nuclear, sintieron “Matz está exagerando”. De hecho, yo también lo sentí así. Pero lo hicimos. Me honra ver que el equipo nuclear en realidad logró hacer que Ruby 3.0 fuera tres veces más rápido que Ruby 2.0 (con algunos puntos de referencia). – Matz

MJIT

Se implementaron muchas mejoras en MJIT. Ver detalles en el archivo NEWS.

Con Ruby 3.0, el compilador justo a tiempo (JIT) se supone que da mejoras en desempeño en cargas de trabajo limitadas, como juegos (Optcarrot), Inteligencia Artificila(Rubykon) o cualquier aplicación que emplee la mayoría del tiempo llamando unos pocos métodos muchas veces.

Aunque Ruby 3.0 disminuyó significativamente el tamaño del código compilado justo a tiempo, aún no está listo para optimizar cargas de trabajo como aplicaciones Rails, que suelen emplear el tiempo en muchos métodos, y por eso sufren de demasiados desatinos con el i-cache debido al JIT. Esté pendiente de Ruby 3.1 que incluirá otras mejoras en esta área.

Concurrencia / Paralelismo

Hoy es un era de múltiples núcleos. La concurrencia es muy importante. Con Ractor, junto con fibras asincronas, Ruby será un lenguaje concurrente real — Matz

Ractor (experimental)

Un Ractor es una abstracción de concurrencia al estilo Actor-modelo, diseñada para brindar una forma de ejecución en paralelo sin preocuparse por la seguridad de los hilos (thread-safe) de ejecución.

Puede crear múltiples ractors y puede ejecutarlos en paralelo. Un Ractor permite hacer programas paralelos con seguridad en los hilos de ejecución porque los ractors no comparten objetos normales. La comunicación entre ractors se soporta mediante envío de mensajes.

Para limitar los objetos que se comparten, Ractor introduce diversas restricciones a la sintaxis de Ruby (no hay cambio cuando no hay múltiples Ractors).

La especificación e implementación no es madura y podría cambiar a futuro, por eso esta característica se señala como experimental y con el primer Ractor.new se presenta una advertencia de característica experimental.

El siguiente programita mide el tiempo de ejecución de un punto de referencia famoso, la función tak (Función Tak - Wikipedia), que se ejecuta 4 veces de manera secuencial o 4 veces en paralelo con ractores.

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)

El resultado se midió en Ubuntu 20.04, con procesador Intel(R) Core(TM) i7-6700 (4 núcleos, 8 hilos por hardware). Muestra que la versión paralela es 3.87 veces más rápida que la versión secuencial.

Vea más detalles en doc/ractor.md.

Planificador (Scheduler) de Fibras

Se introduce Fiber#scheduler para interceptar operaciones que bloquean. Esto permite contar con una concurrencia liviana sin cambiar el código existente. De un vistazo general y vea como funciona en “Don’t Wait For Me, Scalable Concurrency for Ruby 3”.

Los métodos y clases que se soportan en el momento son:

  • Mutex#lock, Mutex#unlock, Mutex#sleep
  • ConditionVariable#wait
  • Queue#pop, SizedQueue#push
  • Thread#join
  • Kernel#sleep
  • Process.wait
  • IO#wait, IO#read, IO#write y métodos relacionados (e.g. #wait_readable, #gets, #puts y así sucesivamente).
  • IO#select no es soportado.

Este programa de ejemplo realizará varias peticiones HTTP concurrentemente:

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

Note que usa async que provee el ciclo de eventos. Este ciclo de eventos usa ganchos Fiber#scheduler para lograr un Net::HTTP no-bloqueante. Otras gemas pueden usar esta interfaz para proveer ejecución no-bloqueante para Ruby, y aquellas gemas pueden ser compatibles con otras implementaciones de Ruby (e.g. JRuby, TruffleRuby) que pueden soportar los mismos ganchos no-bloqueantes.

Análisis Estático

La decada del 2010 fue de lenguajes de programación tipados estaticamente. Ruby busca el futuro con chequeo de tipos estáticos, sin declaración de tipos pero usando interpretación abstracta. RBS & TypeProf son los primeros pasos hacia el futuro. Vendrán más pasos. — Matz

RBS

RBS es un lenguaje para describir los tipos de los programas Ruby.

Los verificadores de tipos, incluyendo TypeProf y otras herramientas que soporten RBS entenderán mejor los programas Ruby que tengan definiciones RBS.

Usted puede escribir la definición de clases y módulos: métodos que se definen en la clase, variables de instancia y sus tipos, y relaciones de herencia/mix-in.

El objetivo de RBS es soportar los patrones que comúnmente se ven en programas Ruby y permitir escribir tipos avanzados incluyendo tipos unión, sobrecarga de métodos y genéricos. También soporta tipado pato (duck typing) con tipos de interfaz.

Ruby 3.0 se distribuye con la gema rbs, que permite analizar y procesar definiciones de tipos escritas en RBS. El siguiente es un pequeño ejemplo de RBS con una clase, un modulo y definiciones de constantes.

module AplicacionMensajeria
  VERSION: String
  class Channel
    attr_reader nombre: String
    attr_reader mensajes: Array[Mensaje]
    attr_reader usuarios: Array[Usuario | Robot]           # `|` significa tipos unión, `Usuario` o `Robot`.

    def initialize: (String) -> void

    def publicar: (String, de: Usuario | Robot) -> Mensaje # Se soporta sobrecarga de métodos.
            | (File, de: Usuaurio | Robot) -> Mensaje
  end
end

Ver más detalles en el archivo README de la gema rbs.

TypeProf

TypeProf es una herramienta para análisis de tipos incluida en el paquete Ruby.

Actualmente, TypeProf sirve como una forma de inferencia de tipos.

Lee código Ruby plano (sin anotiaciones de tipos), analiza que métodos se definen y como se usan, y genera un prototipo de la firma de los tipos en formato RBS.

Aquí hay una simple demostración de TypeProf.

Entrada de ejemplo:

# prueba.rb
class Usuario
  def initialize(nombre:, edad:)
    @nombre, @edad= nombre, edad
  end
  attr_reader :nombre, :edad
end
Usuario.new(nombre: "Juan", edad: 20)

Salida de ejemplo:

$ typeprof prueba.rb
# Classes
class Usuario
  attr_reader nombre : String
  attr_reader edad : Integer
  def initialize : (nombre: String, edad: Integer) -> [String, Integer]
end

Puede ejecutar TypeProf, tras guardar el archivo de entrada como “prueba.rb” tecleando “typeprof prueba.rb”.

También puede probar TypeProf en línea. (TypeProf corre al lado del servidor, así que ¡disculpe si no está operando!)

Ver detalles en la documentación de TypeProf y en las demostraciones.

TypeProf es experimental y aún no es una herramienta madura, sólo soporta un subconjunto del lenguaje Ruby y la detección de errores de tipos es limitada. Pero está creciendo rapidamente para mejorar la cobertura de las características del lenguaje, el desempeño del análisis y la usabilidad. Toda retroalimentación es bienvenida.

Otras características notables

  • El reconocimiento de patrones en una línea se ha rediseñado (experimental)

    • se añade =>. Puede usarse como una asignación al lado derecho.

      0 => a
      p a #=> 0
      
      {b: 0, c: 1} => {b:}
      p b #=> 0
      
    • in se ha cambiado para retornar true o false.

      # version 3.0
      0 in 1 #=> false
      
      # version 2.7
      0 in 1 #=> raise NoMatchingPatternError
      
  • Se agrega un patrón Encontrar (Find). (experimental)

    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
    
  • Se agrega una definición de métodos que no requiere end.

    def cuadrado(x) = x * x
    
  • Hash#except ahora es un método incorporado.

    h = { a: 1, b: 2, c: 3 }
    p h.except(:a) #=> {:b=>2, :c=>3}
    
  • Memory view se agrega como característica experimental

    • Este es un nuevo conjunto de llamados en la API de C para intercambiar áreas de memoria puras, como arreglos numéricos o mapas de bits de imagenes, entre las librerías que son extensiones. Las librerías que son extensiones pueden compartir también los metadatos del área de memoria que constan de la forma, el formato del elemento y así sucesivamente. Usando esta clase de metadatos, las librerías que son extensiones pueden compartir apropiadamente incluso un arreglo multidimensional. Esta nueva característica se diseñó empleando como referencia el protocolo de colchón (buffer ) de Python.

Mejoras en desempeño

  • Pegar código largo en IRB es 53 veces más rápido que con Ruby 2.7.0. Por ejemplo el tiempo requerido para pegar este código de ejemplo pasa de 11.7 segundos a 0.22 segundos.
  • A IRB se le ha añadido la orden measure. Permite una medición sencilla del tiempo de ejecución.

    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
    

Otros cambios notables desde 2.7

  • Los argumentos de palabra clave se separan de otros argumentos.
    • En principio, el código que presente una advertencia en Ruby 2.7 no funcionará. Ver detalles en este documento.
    • Por cierto, el re-envío de argumentos ahora soporta argumentos principales.

      def method_missing(meth, ...)
        send(:"do_#{ meth }", ...)
      end
      
  • El reconocimiento de patrones (case/in) ya no es experimental.
  • La característica $SAFE se eliminó por completo; ahora es una variable global normal.

  • El orden de la traza de llamados (backtrace) se había invertido en Ruby 2.5, pero esto se ha revertido. Ahora las trazas de llamados se comportan como en Ruby 2.4; se imprime primero un mensaje de error y el número de línea donde ocurrió la excepción; las funciones que había hecho la llamada se imprimen después.

  • Se actualizaron algunas librerías estándar.
    • 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.
  • Las siguientes librerías ya no son gemas distribuidas con ruby. Instale las gemas correspondientes para usar sus funcionalidades.
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • Las siguientes gemas por omisión ahora se distribuyen con Ruby.
    • rexml
    • rss
  • Los siguientes archivos de stdlib ahora son gemas y se publicaron en 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

Ver más detalles en el archivo NEWS o en la bitácora de contribuciones

Con estos cambios, 4028 archivos cambiados, 200058 inserciones(+), 154063 eliminaciones(-) desde Ruby 2.7.0!

Ruby3.0 es un hito. El lenguaje ha evolucionado, manteniendo compatibilidad. Pero no es el final. Ruby seguirá progresando, y haciendose más grande. ¡Esté pendiente! — Matz

!Feliz Navidad, Feliz Año Nuevo y disfrute programando con Ruby 3.0!

Descargas

  • 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
    

Qué es Ruby

Ruby fue desarrollado inicialmente por Matz (Yukihiro Matsumoto) en 1993, y ahora es desarrollado como código abierto. Corre en muchas plataformas y se usa en todas partes del mundo especialmente para desarrollo web.