Ruby 3.0.0 现已发布

我们很高兴宣布 Ruby 3.0.0 已发布。自 2015 年起,我们就开始努力开发 Ruby 3,其目标在更高的性能、并发性和更安全的类型。尤其是性能上,Matz 表示「Ruby 3 会比 Ruby 2 快 3 倍」,也就是 Ruby 3x3

Optcarrot 3000 frames

Optcarrot 基准测试中 我们测量了单线程 NES 游戏模拟器工作负载下的性能,其相比 Ruby 2.0 快了 3 倍!

该结果是 Ruby 3.0 在下面的环境中测试得到的(benchmark-driver.github.io/hardware.html )。取决于你所运行的环境和基准测试的类型,可能提升不足 3 倍。

Ruby 3.0.0 的主要目标包括

  • 性能
    • MJIT
  • 并发性
    • Ractor
    • Fiber 调度器
  • 类型(静态分析)
    • RBS
    • TypeProf

除了这些性能提升,Ruby 3 还引入了下面的新特性。

性能

当我第一次在会议上提到「Ruby 3x3」的时候,大多数核心团队的成员都觉得 Matz 在吹牛。事实上,我也这么觉得。但是我们做到了。我很高兴看到核心开发团队完成了 Ruby 3.0 比 2.0 快 3 倍的目标(在一些基准测试中)。—— Matz

MJIT

MJIT 引入了大量的提升。详见 NEWS。

对于 Ruby 3.0 来说,JIT 将能在某些特定的工作负载下得到性能提升,例如游戏(Optcarrot)、AI(Rubykon),以及其它大多数时间都在反复调用某些特定方法的应用中。

虽然 Ruby 3.0 大幅降低了 JIT 后代码的大小,但对例如 Rails 这样的负载还没有做好准备。其调用大量不同的方法,在使用 JIT 后会遇到 CPU 指令缓存命中率降低的问题。Ruby 3.1 会在这一问题上进行进一步优化提升,敬请关注。

并发/并行

现在是多核的时代,并发性很重要。有了 Ractor 和异步 Fiber,Ruby 将会成为一门真正的并发语言。—— Matz

Ractor(实验性)

Ractor 是一个基于 Actor 模型的并发抽象层,提供了无需担心线程安全的并行环境。

你可以创建多个 Ractor,它们能并行运行。Ractor 能保障多线程的线程安全,因为其无法跨线程共享一般对象,需要通过多个 Ractor 之间的通信来交换信息。

为了限制共享对象,Ractor 在 Ruby 语法中引入了多重的限制(如果不使用多个 Ractor 的话,没有这些限制)。

目前的规格和实现尚不完善,并可能在未来发生变更,因此当你第一次调用 Ractor.new 的时候会提示该特性是「实验性」的。

下面的小程序使用了 4 个 Ractor 线程同时执行著名的 Tak 函数(Tak (function) - Wikipedia)测量了其性能。

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 调度器

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 变为非阻塞请求。其它使用该接口 的 gem 也会自动提供非阻塞执行。这样实现的 gem 对于 Ruby 的其它实现了非阻塞的解释器(例如 JRuby, TruffleRuby)也能达到一样的非阻塞运行的效果。

静态分析

21 世纪 10 年代是静态编程语言的时代。Ruby 使用了抽象解释层来探索在没有类型定义的情况下实现类型检查。RBS 和 TypeProf 是迈向这样未来的第一步。未来还有更多发展。—— Matz

RBS

RBS 是一门描述 Ruby 类型的语言。

包括 TypeProf 在内的支持 RBS 的类型检查器能更好通过 RBS 定义来理解 Ruby 程序。

你可以写下类和模块的定义:例如类中的方法、实例变量和它们的类型,以及它们如何组合和继承。

RBS 的目的是支持常见的 Ruby 程序的各种模式,并使其可以描述高级类型,包括:组合类型、函数重载、泛型。其也支持对鸭子类型提供接口类型。

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文件然后调用命令 typeprof test.rb 即可生成。

你也可以 在线尝试 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。例如将数组和位图在两个扩展库中交换。该扩展库可以互相共享其内存空间的元信息,例如尺寸、格式等。利用这些元信息,扩展库之间可以共享例如多维度的数组。该功能参考了 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 起的新特性

  • 关键字参数和其它参数分离。
    • 原则上,在 2.7 会跑出警告的操作在 3.0 不能执行。详见 文档
    • 顺带一提,参数现在支持了前缀参数匹配。

      def method_missing(meth, ...)
        send(:"do_#{ meth }", ...)
      end
      
  • 模式匹配 (case/in) 不再是实验性特性。
  • $SAFE 特性被彻底移除,现在它就是一个普通的全部常量。
  • backtrace 的顺序在 Ruby 2.5 中被颠倒,现在倒了回来。现在其行为和 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.
  • 标准库以及内置的 gem 不再含有下面的 gems 如果要使用相应的特性,请安装对应的 gem
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • 以下 gems 将从默认变为内置
    • rexml
    • rss
  • 以下标准库现变为默认 gem,并在 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提交记录 以获取更多信息。

伴随这些变更,4028 文件已更改,自 Ruby 2.7.0 以来新增 200058 行,删除 154063 行

Ruby 3.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(Yukihiro Matsumoto)于 1993 年开发,现在作为开源软件开发的语言。它可以在多个平台上运行,并在世界各地使用。尤其适合于网站的开发。