Вышел Ruby 3.0.0

Мы рады объявить о выходе Ruby 3.0.0. С 2015 мы упорно работали над Ruby 3, чья цель есть производительность, параллелизм и типизация. Matz особенно подчеркнул производительность: “Ruby3 будет в 3 раза быстрее Ruby2” a.k.a. Ruby 3x3.

Optcarrot 3000 frames

С помощью бенчмарка Optcarrot, который замеряет скорость одного потока на примере эмуляции NES игры, мы достигли трехкратного ускорения производительности по сравнению с Ruby 2.0!

Замеры производились в окружении, описанном в benchmark-driver.github.io/hardware.html. Commit 8c510e4095 стал отметкой Ruby 3.0. В вашем окружении или бенчмарке результат может быть не трехкратным.

Ruby 3.0.0 включает в себя следующие цели

  • Производительность
    • MJIT
  • Параллелизм
    • Рактор
    • Планировщик нитей
  • Типизация (Статический анализ)
    • RBS
    • TypeProf

С вышеуказанными улучшениями производительности Ruby 3.0 также привносит несколько новых фич, которые будут описаны ниже.

Производительность

Когда я впервые объявил о “Ruby3x3” в слайдах на конференции, многие члены команды подумали: “что за бахвальство?”. На самом деле мне тогда тоже так показалось. Но мы сделали это. Для меня честь видеть, что команда действительно смогла сделать Ruby3.0 в три раза быстрее Ruby2.0 (в некоторых бенчмарках). – Matz

MJIT

Многие улучшения были реализованы в MJIT. См. NEWS.

В Ruby версии 3.0 подразумевается, что JIT улучшит производительность в некоторых задачах, таких как игры (Optcarrot), ИИ (Rubykon) или любое другое приложение, которое тратит большую часть времени вызывая одни и те же методы много раз.

Не смотря на то, что Ruby 3.0 значительно уменьшил размер JIT кода, он все еще не готов к оптимизации таких задач, как Rails, где так много методов, что происходит промах в i-cache, особенно при JIT. Следите за новостями о дальнейших улучшениях в Ruby 3.1 на этот счет.

Параллелизм

Сегодня мы живем в мире многоядерности. Параллелизм очень важен. Ractor и Async Fiber сделают Ruby по-настоящему языком параллельного программирования. — Matz

Рактор (экспериментально)

Рактор – это абстракция параллельных вычислений, подобная модели акторов, предназначенная обеспечить параллельное выполнение без лишних хлопот о потоковой безопасности.

Вы можете создать несколько ракторов и запустить их параллельно. Рактор обеспечивает потоковую безопасность параллельных вычислений тем, что не может иметь общих объектов с другими ракторами в обычном понимании. Коммуникация между ракторами обеспечивается передачей сообщений.

Рактор синтаксически ограничивает возможность совместного использования объектов (в случае одного Рактора разницы не будет).

Спецификация и реализация еще не окончательны и могут быть изменены в будущем, поэтому эта фича отмечена как экспериментальная и выдает предупреждение “experimental feature” при первом вызове Ractor.new.

Ниже небольшая программа, вычисляющая n.prime? (n – относительно большое целое число) параллельно двумя ракторами. На компьютере, поддерживающем параллельные вычисления, вы убедитесь, что программа выполняется примерно в 2 раза быстрее, чем последовательная.

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)

Результаты были получены на Ubuntu 20.04, Intel(R) Core(TM) i7-6700 (4 физических ядра, 8 логических ядер). Это показывает, что параллелизированная версия в 3.87 раза быстрее, чем последовательная.

См. doc/ractor.md.

Планировщик нитей

Fiber#scheduler предназначен для перехвата блокирующих операций. Это обеспечивает легковесный параллелизм без необходимости менять существующий код. Обзор того, как это работает, смотрите в видео “Don’t Wait For Me, Scalable Concurrency for Ruby 3”.

Классы/методы, поддерживаемые на данный момент:

  • Mutex#lock, Mutex#unlock, Mutex#sleep
  • ConditionVariable#wait
  • Queue#pop, SizedQueue#push
  • Thread#join
  • Kernel#sleep
  • Process.wait
  • IO#wait, IO#read, IO#write и смежные (#wait_readable, #gets, #puts и пр.).
  • IO#select не поддерживается.

Этот пример выполнит несколько запросов HTTP параллельно:

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

Он использует гем async который обеспечивает цикл событий. Этот цикл событий использует хуки Fiber#scheduler для того, чтобы сделать Net::HTTP неблокирующим. Другие гемы также могут использовать этот интерфейс для обеспечения неблокирующего выполнения кода на Ruby, и эти гемы могут быть совместимыми другими реализациями Ruby (например, JRuby, TruffleRuby), которые могут поддерживать такие же неблокирующие хуки.

Статический анализ

2010-е были эпохой статически типизированных языков программирования. Ruby смотрит в будущее статической типизации не через объявление типов, а через абстрактную интерпретацию. RBS и TypeProf – это первые шаги в будущее. Впереди еще много шагов. — Matz

RBS

RBS – это язык описания типов в программах на Ruby.

Тайп-чекеры, включая TypeProf и другие инструменты, поддерживающие RBS, будут понимать программы на Ruby намного лучше с определениями RBS.

Вы можете написать определения классам и модулям: методам, определенным в классе, переменным экземпляра и их типам, а также отношениям наследования/примесей.

RBS преследует цель поддержать наиболее часто встречающиеся паттерны программирования на Ruby и позволяет описывать сложные типы, такие как объединения, перегрузка методов и дженерики. Также поддерживается утиная типизация с интерфейсами.

Ruby 3.0 поставляется с гемом rbs, который позволяет парсить и обрабатывать определения типов, написанные на языке RBS. Ниже небольшой пример RBS с определениями класса, модуля и константы.

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

См. README гема rbs.

TypeProf

TypeProf – это инструмент статического анализа, поставляемый вместе с пакетом Ruby.

На данный момент TypeProf как бы выполняет вывод типов.

Он читает обычный (без аннотаций типов) код на Ruby, анализирует, как методы объявлены и используются, и генерирует прототип аннотаций типов в формате RBS.

Вот небольшая демонстрация TypeProf.

Пример ввода:

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

Пример вывода:

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

Вы можете воспользоваться TypeProf сохранив ввод в файл test.rb, а затем выполнив команду typeprof test.rb.

Также вы можете попробовать TypeProf онлайн. (Там TypeProf запускается на сервере – приносим свои извинения, если он лежит!)

См. документацию по TypeProf и демо.

TypeProf – это еще пока только экспериментальная разработка; поддерживается лишь подмножество языка Ruby, и определение типов ограничено. Однако она быстро растет и улучшает покрытие фич языка, скорость анализа и удобство использования. Любая обратная связь крайне приветствуется.

Другие значимые новые фичи

  • Однострочный поиск по образцу изменил свой вид. (экспериментально)

    • добавлен => для правостороннего присваивания.

      0 => a
      p a #=> 0
      
      {b: 0, c: 1} => {b:}
      p b #=> 0
      
    • in теперь возвращает true или false.

      # version 3.0
      0 in 1 #=> false
      
      # version 2.7
      0 in 1 #=> raise NoMatchingPatternError
      
  • Поиск по образцу. (экспериментально)

    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
    
  • Добавлена возможность объявления метода без end.

    def square(x) = x * x
    
  • Hash#except теперь в stdlib.

    h = { a: 1, b: 2, c: 3 }
    p h.except(:a) #=> {:b=>2, :c=>3}
    
  • Просмотр памяти добавлен как экспериментальная фича

    • Это новое C-API для прямого обмена участками памяти, такими как числовой массив или битмап, между библиотеками нативных расширений. Библиотеки расширений также могут делиться метаданными об участках памяти, таких как форма, формат и пр. С помощью этих метаданных библиотеки расширений могут делиться даже многомерными массивами. Дизайн этой фичи был вдохновлен буферным протоколом Python.

Улучшения производительности

  • Вставка длинного кода в IRB в 53 раза быстрее, чем в IRB от Ruby 2.7.0. Например, время, необходимое для вставки этого кода уменьшилось с 11.7 секунды до 0.22 секунды.
  • В IRB добавлена команда measure. Она позволяет легко замерять время выполнения.

    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
    

Другие значимые отличия от 2.7

  • Именованные параметры отделены от остальных.
    • Код, который выдавал предупреждения в Ruby 2.7, работать больше не будет. См. новость.
    • Кстати, теперь можно брать только ведущие аргументы.

      def method_missing(meth, ...)
        send(:"do_#{ meth }", ...)
      end
      
  • Поиск по образцу (case/in) – больше не экспериментальная фича.
  • Фича $SAFE полностью выпилена; теперь это обычная глобальная переменная.
  • В Ruby 2.5 бектрейс был развернут, но это изменение откатили. Теперь он ведет себя как в Ruby 2.4; сначала идет сообщение об ошибке и номер строки, где возникло исключение, а затем следует стек вызовов.
  • Обновлены некоторые стандартные библиотеки.
    • 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.
  • Следующие библиотеки более не поставляются с языком. Устанавливайте их соответствующими гемами.
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • Следующие, поставляемые с языком, гемы больше не встроены в него.
    • rexml
    • rss
  • Следующие файлы стандартной библиотеки переведены в разряд встроенных гемов и опубликованы на 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

См. NEWS или коммиты.

В ходе этого со времен версии 2.7.0 было изменено 4028 файлов, было сделано 200058 вставок(+), 154063 удалений(-)!

Ruby3.0 – это веха. Язык эволюционировал, сохранив совместимость. Но это еще не все. Ruby будет развиваться и станет еще лучше. Следите за новостями! — Matz

С Рождеством, с праздниками, и получайте удовольствие от программирования на Ruby 3.0!

Скачать

  • 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

Matz (Yukihiro Matsumoto) разработал первую версию Ruby в 1993, и на сегодня язык разрабатывается как Open Source. Он работает на множестве платформ и используется по всему миру, особенно в веб разработке.