Lanzamiento de Ruby 3.3.0
- Se lanzó la versión Ruby 3.3.0. Incorpora Prism, un nuevo parser; usa Lrama como generador de parsers; agrega RJIT, un compilador JIT escrito en Ruby puro; y, en particular, incluye mejoras de rendimiento en YJIT.
Parser Prism
- Prism es un parser recursivo descendente para el lenguaje Ruby, portable, resistente a errores y fácil de mantener, y se ofrece como gem por defecto.
- Prism es apto para producción, se mantiene activamente y puede usarse como reemplazo de Ripper.
- Se ofrece documentación detallada sobre cómo usar Prism.
- Prism es una biblioteca en C usada dentro de CRuby y también un gem de Ruby utilizable por cualquier herramienta que necesite parsear código Ruby.
- Entre los métodos principales de la API de Prism están
Prism.parse(source), Prism.parse_comments(source) y Prism.parse_success?(source).
- Se puede contribuir enviando pull requests o issues directamente al repositorio de Prism.
- Para usar experimentalmente el compilador Prism se puede usar
ruby --parser=prism o RUBYOPT="--parser=prism", aunque solo debería usarse con fines de depuración.
Generador de parsers Lrama
- Bison fue reemplazado por el generador de parsers LALR Lrama.
- Quienes estén interesados pueden revisar la visión futura del parser de Ruby.
- Para facilitar el mantenimiento, el parser interno de Lrama fue reemplazado por un parser LR generado por Racc.
- Soporta reglas parametrizadas (?, *, +), que se usarán en el archivo parse.y de Ruby.
YJIT
- Hay mejoras importantes de rendimiento frente a Ruby 3.2.
- Se mejoró el soporte para argumentos splat y rest.
- Se asignan registros para las operaciones de pila de la máquina virtual.
- Se compilan más llamadas con argumentos opcionales. También se compilan los manejadores de excepciones.
- Los tipos de llamada no soportados y los sitios de llamada megamórficos ya no vuelven al intérprete.
- Métodos base como
#blank? de Rails y el caso especial de #present? ahora se procesan inline.
- Se optimizan especialmente
Integer#*, Integer#!=, String#!=, String#getbyte, Kernel#block_given?, Kernel#is_a?, Kernel#instance_of? y Module#===.
- La velocidad de compilación es un poco mayor que en Ruby 3.2.
- En Optcarrot, es más de 3 veces más rápido que el intérprete.
- El uso de memoria mejoró considerablemente frente a Ruby 3.2.
- Los metadatos del código compilado usan mucha menos memoria.
--yjit-call-threshold sube automáticamente de 30 a 120 para aplicaciones con más de 40,000 ISEQ.
- Se añadió
--yjit-cold-threshold para omitir la compilación de ISEQ fríos.
- En Arm64 se genera código más compacto.
- El code GC está desactivado por defecto.
--yjit-exec-mem-size se trata como un límite duro a partir del cual se detiene la compilación de nuevo código.
- No hay degradación de rendimiento por code GC, y al usar Pitchfork se observa un mejor comportamiento copy-on-write cuando el servidor hace re-fork.
- Si se desea, el code GC puede activarse con
--yjit-code-gc.
- Se añadió
RubyVM::YJIT.enable para activar YJIT en tiempo de ejecución.
- Rails 7.2 planea usar este método para activar YJIT por defecto.
- Este método puede usarse para activar YJIT solo después de que la aplicación termine de arrancar.
- Para desactivar YJIT al inicio mientras se usan otras opciones de YJIT, puede usarse
--yjit-disable.
- Ahora se ofrecen más estadísticas de YJIT por defecto.
yjit_alloc_size y varias estadísticas relacionadas con metadatos se ofrecen por defecto.
- La estadística
ratio_in_yjit generada por --yjit-stats está disponible en builds de lanzamiento. Ya no hacen falta estadísticas especiales ni builds de desarrollo.
- Se agregaron más capacidades de profiling.
- Se añadió
--yjit-perf para facilitar el profiling con Linux perf.
--yjit-trace-exits soporta muestreo usando --yjit-trace-exits-sample-rate=N.
- También hubo pruebas más exhaustivas y numerosas correcciones de errores.
RJIT
- Se incorpora RJIT, un compilador JIT escrito en Ruby puro, y reemplaza a MJIT.
- RJIT solo está soportado en arquitectura x86-64 sobre plataformas Unix.
- A diferencia de MJIT, no requiere un compilador de C en tiempo de ejecución.
- RJIT existe solo con fines experimentales.
- En producción se debe seguir usando YJIT.
- Si te interesa el desarrollo de JIT en Ruby, recomiendan revisar la charla de k0kubun en el día 3 de RubyKaigi.
Scheduler de hilos M:N
- Se introduce un scheduler de hilos M:N.
- Como M hilos de Ruby son gestionados por N hilos nativos (hilos del sistema operativo), se reduce el costo de crear y administrar hilos.
- El scheduler de hilos M:N está desactivado por defecto en el Ractor principal porque puede romper la compatibilidad con extensiones en C.
- Se puede activar en el Ractor principal usando la variable de entorno
RUBY_MN_THREADS=1.
- En los Ractor no principales, los hilos M:N siempre están activados.
- La variable de entorno
RUBY_MAX_CPU=n define el máximo de N (cantidad máxima de hilos nativos). El valor por defecto es 8.
- Como solo puede ejecutarse un hilo de Ruby por Ractor, las aplicaciones de un solo Ractor (la mayoría) usan solo 1 hilo nativo.
- Puede usarse una cantidad de hilos nativos mayor que N para soportar operaciones bloqueantes.
Mejoras de rendimiento
defined?(@ivar) fue optimizado usando Object Shapes.
- La resolución de nombres como
Socket.getaddrinfo ahora puede interrumpirse (en entornos compatibles con pthreads).
- Hay varias mejoras de rendimiento en el recolector de basura.
- Cuando un objeto joven es referenciado por un objeto viejo, ya no se promueve inmediatamente a la generación vieja, lo que reduce mucho la frecuencia de recolecciones GC mayores.
- Se introdujo una nueva variable de ajuste,
REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO, para controlar la cantidad de objetos no protegidos que disparan una recolección GC mayor. Su valor por defecto es 0.01 (1%), lo que reduce notablemente la frecuencia de estas recolecciones.
- Se implementaron Write Barriers para muchos tipos base que no las tenían. Esto reduce considerablemente el tiempo de recolecciones GC menores y la frecuencia de las mayores.
- La mayoría de las clases base ahora usan Variable Width Allocation. Esto acelera la asignación y liberación de esas clases, reduce el uso de memoria y disminuye la fragmentación del heap.
- Se añadió soporte de referencias débiles al recolector de basura.
Otros cambios destacados
- IRB recibió varias mejoras, incluyendo integración avanzada con irb:rdbg, soporte de paginación para los comandos ls, show_source y show_cmds, mayor precisión y utilidad de la información mostrada por ls y show_source, y autocompletado experimental usando análisis de tipos.
- IRB también pasó por una amplia refactorización para facilitar futuras mejoras y recibió decenas de correcciones de errores.
Problemas de compatibilidad
- El uso de
it dentro de bloques sin argumentos ya no se recomienda y, en Ruby 3.4, pasará a referirse al primer parámetro del bloque.
- Se eliminaron variables de entorno en desuso.
Actualizaciones de la biblioteca estándar
- RubyGems y Bundler mostrarán advertencias si el usuario requiere los siguientes gems sin agregarlos al Gemfile o al gemspec, ya que esos gems pasarán a ser bundled gems en futuras versiones de Ruby.
- Se agregaron o actualizaron los siguientes gems por defecto: prism 0.19.0, RubyGems 3.5.3, abbrev 0.1.2, entre muchos otros.
- Los siguientes bundled gems fueron promovidos desde gems por defecto o actualizados: racc 1.7.3, minitest 5.20.0, entre muchos otros.
Opinión de GN⁺
- Introducción del parser Prism: una de las características más importantes de Ruby 3.3.0 es la incorporación del nuevo parser Prism. Esto será de gran ayuda para los desarrolladores de Ruby al ofrecer un parser más eficiente para procesar código Ruby, resistente a errores y fácil de mantener.
- Mejoras de rendimiento en YJIT: las principales mejoras de rendimiento en YJIT aumentarán notablemente la velocidad de ejecución de las aplicaciones Ruby, y en particular la reducción del uso de memoria y las optimizaciones del GC tendrán un impacto positivo en el rendimiento y la estabilidad de aplicaciones Ruby de gran escala.
- Scheduler de hilos M:N: la introducción del scheduler de hilos M:N tiene el potencial de mejorar el rendimiento de las aplicaciones Ruby multihilo. Esto reducirá el costo de administración de hilos y permitirá un procesamiento paralelo más eficiente.
1 comentarios
Opiniones de Hacker News
Con la llegada de Ruby 3.3, Ruby, un lenguaje que prioriza la felicidad del desarrollador, se sacude su antigua imagen de ser lento y ahora presume una gran velocidad.
Ruby 3.3 es considerado el lanzamiento más importante y con más funcionalidades de los últimos 10 años, y sorprende que haya lanzado un JIT antes que Python.
Se informa que Ruby 3.3 ya puede usarse en Heroku.
Cada Navidad, el lenguaje Ruby lanza una nueva versión.
Se plantea la pregunta de si vale la pena aprender Ruby si ya se conocen Python y NodeJS. Ruby parece atractivo, pero también se siente difícil.
La resolución de nombres, como
Socket.getaddrinfo, puede bloquearse. Cada vez que se necesita resolver nombres, se crea un pthread worker y se ejecutagetaddrinfo(3).Prism parece interesante. Se pregunta si hay ejemplos de uso de Prism como herramienta para analizar código Ruby.
La variable de entorno
RUBY_MAX_CPU=nestablece la cantidad máxima de hilos nativos. El valor predeterminado es 8.Se están buscando buenos ejemplos con Prism. Hay decepción porque en la página de lanzamiento no parece haber mucho más aparte de las “APIs notables”.
Se menciona que es el regalo de Navidad perfecto.