Ruby 3.0.0 發布

我們很高興宣布 Ruby 3.0.0 發布了。從 2015 年開始開發,目標是提高效能、支援並行性並提供型態檢查。Matz 表示「Ruby 3 將會比 Ruby 2 快 3 倍」,即 Ruby 3x3

Optcarrot 3000 frames

Optcarrot 指標(量測了單線程在 NES 遊戲模擬器的效能)來看,我們得到了比 Ruby 2.0 高三倍的效能!

在這裡 benchmark-driver.github.io/hardware.html 所記載的硬體環境下實測。Ruby 3.0 測的是這個 Commit 8c510e4095。但在你的機器上跑起來可能沒有三倍快。

Ruby 3.0.0 達成各項目標的功能

  • 效能
    • MJIT
  • 並行
    • Ractor
    • Fiber Scheduler
  • 靜態型態(靜態分析)
    • RBS
    • TypeProf

通過上述的效能提升,Ruby 3.0 也加入了許多新功能。

效能

當我第一次在大會上放話 “Ruby3x3” 時,許多核心成員認為 “Matz 在說大話”。實際上我自己也是這麼認為的。但我們做到了。能看到核心成員在某些測試實現了 Ruby 3.0 比 Ruby 2.0 快三倍的目標,真是我的榮幸。— Matz

MJIT

許多的效能改進來自 MJIT。參考 NEWS 了解更多。

Ruby 3.0 開啟 JIT 之後,某些場合應該能感覺到效能提升了,比如遊戲領域(Optcarrot)、AI (Rubykon),或是任何需要大量呼叫某些特定方法的應用程式。

雖然 Ruby 3.0 大幅減少了 JIT 產生的程式碼大小,但還不能應用在像是 Rails 這種呼叫大量不同方法的上(因為 CPU 指令 cache 命中率很低),Ruby 3.1 將會改善這一塊,敬請期待。

Concurrency / Parallel

現在是多核心的時代了。並行性至關重要。有了 Ractor 和 Async Fiber 後,Ruby 將會成為一門真正的並行語言。— Matz

Ractor(實驗性)

Ractor 是基於 Actor 模型的並行抽象層,可以並行執行程式而無須擔心線程安全性問題。

可以一次創好幾個 Ractor,也可以同時執行它們。Ractor 之間不共享物件,所以能確保線程的安全性。Ractor 透過交換訊息來互相溝通。

為了要限制共享物件,Ractor 引入了許多 Ruby 語法的限制(若沒有使用多個 Ractor 則沒有限制)。

規範與實作尚未成熟,在未來還有可能會改變,所以 Ractor 還是個實驗性功能,在第一次執行 Ractor.new 時會輸出警告訊息。

以下的小程式量測有名的 tak 函數,分別紀錄了序行執行四次跟用 Ractor 並行執行四次的執行時間。

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
測試結果:
          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

引進了 Fiber#scheduler 來攔截阻塞式操作。可以不改原來的程式碼來獲得少量的並行性。詳見 “Don’t Wait For Me, Scalable Concurrency for Ruby 3” 演講來了解大概的工作原理。

目前支援的類別和方法:

  • Mutex#lockMutex#unlockMutex#sleep
  • ConditionVariable#wait
  • Queue#popSizedQueue#push
  • Thread#join
  • Kernel#sleep
  • Process.wait
  • IO#waitIO#readIO#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 的鉤子(hooks)把 Net::HTTP 變成了非阻塞操作。別的 Gem 也可以用這個介面來實作非阻塞操作,如此一來只要其他的 Ruby(JRuby、TruffleRuby)也實作了非阻塞的鉤子,也就可以達到同樣的效果。

靜態分析

2010 是靜態型態語言的時代。Ruby 透過無需定義型態的抽象解釋層,跟上時代的腳步也實作了靜態型態。RBS 和 TypeProf 只是未來的一小步,之後還有更多發展。— Matz

RBS

RBS 是描述 Ruby 程式型態的語言。

型態檢查工具,TypeProf 和其他工具可以透過 RBS 定義來更好地理解 Ruby 程式。

你可以寫下類別和模組的定義:定義了什麼方法、實體變數和型態,繼承和 mix-in 關係。

RBS 的目標是支援各種常見的 Ruby 程式模式,進而描述更高階的型態如 union、方法重載和泛型。

Ruby 3.0 搭載了 rbs Gem,可以直接解析和處理用 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

參見 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 丟給 TypeProftypeprof test.rb

也可以在線上試用 TypeProf(在我們自己的伺服器上跑 TypeProf,要是壞了在這裡先說聲抱歉)。

參見 TypeProf 文件演示來了解更多。

TypeProf 仍是實驗性質功能尚未成熟,只支援 Ruby 的部分語法,能找出的型態錯誤有限。但目前正在快速開發中來支援更多的 Ruby 特性,更快的分析速度,更高的使用性。有任何回饋歡迎告訴我們。

其它值得一提的新功能

  • 重新設計了單行模式匹配(實驗性質)

    • 新增 =>。用來向右賦值。

      0 => a
      p a #=> 0
      
      {b: 0, c: 1} => {b:}
      p b #=> 0
      
    • in 現在會回傳 truefalse

      # 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 現已內建

    h = { a: 1, b: 2, c: 3 }
    p h.except(:a) #=> {:b=>2, :c=>3}
    
  • 新增了實驗性質的記憶體監視圖

    • 這是一組新的 C-API,用來交換記憶體空間的,比如擴展函式庫(extension)之間可以交換 array 跟 bitmap。擴展函式庫之間也可以共享記憶體空間的 metadata,比如那裡的記憶體已經被用了、佔用空間所存放的格式等。利用這些 metadata,擴展函式庫甚至可以恰當的共享多維度的 array。這個功能參考了 Python 的 buffer protocol。

效能改善

  • 貼一段很長的程式碼到 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 以來的變更

  • Keyword arguments 從其它的參數分離出去了。
    • 原則上來說,在 Ruby 2.7 有警告的程式碼,在 3.0 不會動。參見這份文件來了解更多。
    • 除此之外,參數現在可以轉發到下一層。

      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
    • 等等;
  • 以下函式庫從標準函式庫移除。如果用到了以下函式庫的功能,請安裝對應的 Gem 再使用。
    • 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

參見 NEWScommit logs 來了解更多。

自 2.7.0 以來,計 4028 檔案變更,200058 行新增(+),154063 行刪減(-)

Ruby 3.0 是一個里程碑。語言進化了仍向下相容。這不是終點而是起點。Ruby 會繼續演進變得更好。敬請期待!

聖誕快樂、佳節愉快,享受用 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(Yukihiro Matsumoto)於 1993 年開發的開源軟體。可以在許多平台上執行。使用者來自世界各地,特別活躍於網路開發領域。