Ruby 3.0.0 发布

我们很高兴地宣布 Ruby 3.0.0 的发布。自 2015 年以来,我们一直努力开发 Ruby 3,其目标是性能、并发和类型。特别是关于性能,Matz 曾说过“Ruby3 的速度将是 Ruby2 的 3 倍”,也就是 Ruby 3x3

Optcarrot 3000 frames

通过 Optcarrot 基准测试,该测试基于 NES 游戏模拟工作负载测量单线程性能,它实现了比 Ruby 2.0 快 3 倍的性能!

这些是在 benchmark-driver.github.io/hardware.html 中所述的环境下测量的。提交 8c510e4095 被用作 Ruby 3.0。根据您的环境或基准测试,速度可能不会快 3 倍。

Ruby 3.0.0 通过以下方式涵盖了这些目标

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

在上述性能改进的基础上,Ruby 3.0 引入了以下描述的几个新特性。

性能

当我第一次在会议主题演讲中宣布“Ruby3x3”时,包括核心团队成员在内的许多人都觉得“Matz 是在吹牛”。事实上,我自己也这么觉得。但我们做到了。我感到很荣幸看到核心团队实际上实现了使 Ruby3.0 比 Ruby2.0 快三倍(在某些基准测试中)。—— Matz

MJIT

MJIT 中实现了许多改进。有关详细信息,请参阅 NEWS。

截至 Ruby 3.0,JIT 应该在有限的工作负载中提供性能改进,例如游戏(Optcarrot)、AI(Rubykon)或任何将大部分时间花在多次调用少数方法的应用程序。

尽管 Ruby 3.0 显著减少了 JIT 编译代码的大小,但它仍然没有为优化像 Rails 这样的工作负载做好准备,这些工作负载通常会花费大量时间在许多方法上,因此会因 JIT 加剧的 i-cache 未命中而受损。请继续关注 Ruby 3.1,以获取有关此问题的进一步改进。

并发/并行

如今是多核时代。并发非常重要。借助 Ractor 以及异步 Fiber,Ruby 将成为一种真正的并发语言。—— Matz

Ractor(实验性)

Ractor 是一种类似于 Actor 模型的并发抽象,旨在提供并行执行功能,而无需担心线程安全问题。

您可以创建多个 ractor,并且可以并行运行它们。Ractor 使您能够创建线程安全的并行程序,因为 ractor 无法共享普通对象。ractor 之间的通信通过交换消息来实现。

为了限制对象的共享,Ractor 对 Ruby 的语法引入了一些限制(如果没有多个 Ractor,则没有限制)。

规范和实现尚未成熟,将来可能会发生变化,因此此功能被标记为实验性,并且当第一次出现 Ractor.new 时会显示“实验性功能”警告。

以下小程序测量了著名的 tak 函数的执行时间(Tak (函数) - 维基百科),通过顺序执行 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 核,8 个硬件线程)上测量的。结果表明,并行版本比顺序版本快 3.87 倍。

有关更多详细信息,请参阅 doc/ractor.md

Fiber 调度器

引入了 Fiber#scheduler 来拦截阻塞操作。这允许在不更改现有代码的情况下实现轻量级并发。观看 “不要等我,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 可以使用此接口为 Ruby 提供非阻塞执行,并且这些 gem 可以与 Ruby 的其他实现(例如 JRuby、TruffleRuby)兼容,后者可以支持相同的非阻塞钩子。

静态分析

2010 年代是静态类型编程语言的时代。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。(它在服务器端运行 TypeProf,所以如果它坏了,请原谅!)

有关详细信息,请参阅 TypeProf 文档演示

TypeProf 是实验性的,尚未完全成熟;仅支持 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
    
  • 添加了无限方法定义。

    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 的缓冲协议设计的。

性能改进

  • 将长代码粘贴到 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 以来的其他值得注意的更改

  • 关键字参数与其他参数分开。
    • 原则上,在 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
    • 等等。
  • 以下库不再是捆绑的 gem 或标准库。安装相应的 gem 以使用这些功能。
    • sdbm
    • webrick
    • net-telnet
    • xmlrpc
  • 以下默认 gem 现在是捆绑的 gem。
    • rexml
    • rss
  • 以下 stdlib 文件现在是默认 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提交日志

自 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 年开发,现在以开源形式进行开发。它可在多个平台上运行,并在世界各地广泛使用,尤其是在 Web 开发领域。