Ruby 3.0.0 リリース

Ruby 3.0系初のリリースである、Ruby 3.0.0 が公開されました。

これまで、Ruby3に向けてパフォーマンスの改善、並行処理、静的解析という3つの目標を掲げて、活発に開発が行われてきました。特にパフォーマンスの改善については、Ruby 3x3 として「Ruby3はRuby2の3倍速くする」ことを目指してきました。

Optcarrot 3000 frames

Ruby 3.0では開発の指標の一つとしてきたOptcarrotベンチマークで3倍を達成するとともに、以下のような取り組みが行われています。

benchmark-driver.github.io/hardware.html に書かれている環境で計測されました。 8c510e4095 が Ruby 3.0 として使われています。環境やベンチマークによっては3倍にならないかもしれません。

Ruby 3 では以下の目標を達成しました。

  • パフォーマンスの改善
    • MJIT
  • 並行処理
    • Ractor
    • Fiber Scheduler
  • 静的解析
    • RBS
    • TypeProf

他にも、Ruby 3.0 では Ruby 2.7 に比べて、多くの新しい機能やパフォーマンスの改善が含まれます。 その一部を以下に紹介します。

パフォーマンスの改善

わたしが最初にRuby3x3というスローガンを宣言した時、コアチームメンバーを含むほとんどの人は、「またそんな夢物語を」と思ったことでしょう。実際、わたしもそう思ってました。しかし、皆の努力によって(ある程度)達成できました。この成果をこころから誇りに思います。 — Matz

MJIT

様々な改善がMJITに実装されました。詳細はNEWSをご覧ください。

Ruby 3.0の時点では、JITはいくつかの限られたワークロードで性能を改善します。例えば ゲーム (Optcarrot)、AI (Rubykon)、その他多くの時間を少数のメソッドをたくさん呼び出すことに費すアプリケーションなどが挙げられます。

Ruby 3.0では生成コードのサイズを大幅に削減したものの、Railsのような、様々なメソッドを満遍なく呼び出すi-cacheへの負荷が大きいワークロードでは、JITがその負荷を大きくしてしまうため性能を改善できる状態にはまだ至っていません。Ruby 3.1での改善にご期待ください。

Concurrency / Parallel

マルチコア時代と呼んでも過言ではない現代、コンカレンシーは非常に重要です。RactorとAsync Fiberの導入でようやくRubyもコンカレント言語になれそうです。 – Matz

Ractor (experimental)

Ractor はアクターモデル風の並行・並列制御機構であり、スレッド安全に関する懸念なく、Rubyで並列処理を行うための機能として設計されています。

複数のRactorを作成すると、それらは並列計算機上で並列に実行されます。Ractor間では、ほとんどのオブジェクトが共有できないように設計されているため、スレッド安全なプログラムにすることができます。メッセージの送受信により、Ractor間のコミュニケーションを行うことが可能です。

Ractor間でのオブジェクトの共有を制限するために、複数Ractorでの実行時には、いくつかのRubyの機能に制限が入ります(ただし、複数のRactorを用いない場合には、これまでのRubyと何も変わりません)。

Ractorの仕様と実装は、まだ発展途上であるため、実験的機能として提供されます。初回のRactorの生成時には実験的機能であることが警告で表示されます。

次の小さなプログラムでは、有名なベンチマーク用関数である竹内関数(竹内関数 - Wikipedia )を用いて、4回逐次的に実行する場合と、Ractorを用いて4並列で実行する場合で、それぞれ実行時間を計測しています。

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 cores, 8 hardware threads) で実行したものになります。逐次実行したときよりも、並列化によって3.87倍の高速化していることがわかります。

より詳細は、doc/ractor.md をご覧ください。

Fiber Scheduler

I/Oなど、処理をブロックさせる操作をフックするための 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 と、関連するメソッド (e.g. #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 を用いています。このイベントループは、Net::HTTP をノンブロックとするために、Fiber#scheduler フックを用いています。他の gem も、このインターフェースを用いることで、Ruby をノンブロックで実行することができます。そして、それらの gem は、このインターフェースに対応しているその他の Ruby 実装(例えば、JRuby や TruffleRuby)でも互換にすることができます。

静的解析

2010年代は静的型言語の時代でした。Rubyは抽象解釈を武器に、型宣言なしで静的型チェックする未来を目指します。RBSとTypeProfはその第一歩です。Rubyがもたらす誰も見たことがない静的型の世界を見守ってください — Matz

RBS

RBSはRubyプログラムの型を記述するための言語です。

TypeProfなどの型検査ツールを初めとする静的解析を行うツールは、RBSを利用することでRubyプログラムをより精度良く解析することができます。

RBSでは、Rubyプログラムのクラスやモジュールの型を定義します。メソッドやインスタンス変数、定数とその型、継承やmixinなどの関係などが記述できます。

RBSはRubyプログラムに頻出するパターンをサポートするように設計されており、ユニオン型、メソッドオーバーロード、ジェネリクスなどの機能を提供します。さらに「インタフェース型」によってダックタイピングをサポートします。

Ruby 3.0には、このRBS言語で書かれた型定義を処理するためのライブラリである rbs gemが同梱されています。

クラスやモジュール、定数を定義する、簡単なRBSの例を示します。

module ChatApp
  VERSION: String
  class Channel
    attr_reader name: String
    attr_reader messages: Array[Message]
    attr_reader users: Array[User | Bot]              # `|` は `User` か `Bot` のインスタンスを表現する「ユニオン型」です
    def initialize: (String) -> void
    def post: (String, from: User | Bot) -> Message   # メソッドオーバーロードを記述することもできます
            | (File, from: User | Bot) -> Message
  end
end

詳細については、rbs gemのREADMEを参照してください。

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

サンプル入力を”test.rb”という名前で保存し、typeprof test.rbというコマンドで TypeProf の解析ができます。

TypeProf をオンラインで試すこともできます(サーバサイドで TypeProf を動かしているので、サーバが落ちたらごめんなさい)。

詳しくはドキュメントデモを見てください。

残念ながら TypeProf はまだ実験的で、あまり完成度は高くありません。Ruby 言語のサブセットだけがサポートされていて、型エラー検出の機能は限定的です。ですがいま急速に改良中であり、言語機能のカバレッジ増強、解析効率の向上、利便性の向上などを行っています。フィードバックを歓迎します。

その他の主要な新機能

  • 1行パターンマッチが再設計されました。 (experimental)

    • => を新たに使うようになりました。右代入のように使うことができます。

      0 => a
      p a #=> 0
      
      {b: 0, c: 1} => {b:}
      p b #=> 0
      
    • intrue または false を返すようになりました。

      # version 3.0
      0 in 1 #=> false
      
      # version 2.7
      0 in 1 #=> raise NoMatchingPatternError
      
  • 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
    
  • 一行メソッド定義が書けるようになりました。

    def square(x) = x * x
    
  • Hash#except が組み込みになりました。

    h = { a: 1, b: 2, c: 3 }
    p h.except(:a) #=> {:b=>2, :c=>3}
    

パフォーマンスの改善

  • IRB への長いコードの貼り付けは、Ruby 2.7.0 にバンドルされているものと比較して 53 倍の速さになります。例えば、このサンプルコードの貼り付けに要する時間は、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 からの変更点

  • キーワード引数が通常の引数から分離されました。
    • 原則として、2.7 で警告の出ていたコードは動かなくなります。詳細は別ドキュメントを参照してください。
    • 関連して、引数のフォワーディングの記法で先頭に引数を書けるようになりました。

      def method_missing(meth, ...)
        send(:"do_#{ meth }", ...)
      end
      
  • パターンマッチ(case/in)が実験的な機能ではなくなりました。
  • $SAFE の機能が完全に削除され、ただのグローバル変数となりました。
  • バックトレースの順序は2.5で逆転しましたが、3.0ではこれを取りやめることになりました。例外が起きた行が先に表示され、呼び出し元が後に表示されるように戻ります。
  • いくつかの標準ライブラリがアップデートされました。
    • 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.
  • 以下のライブラリは標準添付ライブラリから削除されました。3.0 以降で使いたい場合は rubygems から利用してください。
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • 以下のライブラリが新たに bundled gems になりました。Bundler から利用する場合は Gemfile に明示的に指定してください。
    • rexml
    • rss
  • 以下のライブラリが新たに default gems になりました。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 ファイルまたはコミットログを参照してください。

なお、こうした変更により、Ruby 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 とは

Rubyはまつもとゆきひろ (Matz) によって1993年に開発が始められ、今もオープンソースソフトウェアとして開発が続けられています。Rubyは様々なプラットフォームで動き、世界中で、特にWebアプリケーション開発のために使われています。