对于所有Rubyists来说,2020年是特殊的一年。难道不是这样么?Ruby 2于2013年发布,我们使用Ruby 2.x已有7年之久,我们一直在等待Ruby 3的发布。
终于,等待结束了。我们终于在圣诞期间迎来了Ruby 3.0.0,它为这种高级通用编程语言提供了更高的性能和其他功能,这不啻给我们最好的圣诞节礼物。现在是时候拆开礼品盒了,看看我们得到的所有Ruby 3功能。
Ruby 3.0的开发着眼于更高的性能、并发性和类型,并成功实现了比Ruby 2.0的性能快3.0倍的目标。3.0倍速是在使用新的Ruby 3.0的Just-In-Time(JIT)运行时编译器时实现的,但与Ruby 2相比,就其VM实现而言,仍然是相当可观的提速。
也许有人会问,为什么把Ruby 3.0的性能提速跟Ruby 2.0对比,而不是诸如Ruby 2.7?请去官网阅读发行说明,将性能提高3倍是2015年的既定目标。
Ruby3.0的JIT表现出非常出色的性能,非常适合需要多次调用几种方法的工作负载。Ruby 3.1有望为需要更多调用方法的工作负载提高JIT性能。
Ruby 3.0还为并行执行功能提供了实验性的"Ractor",而无需考虑线程安全性;Fiber Scheduler允许拦截阻塞操作、改进静态分析、改进的单行模式匹配以及许多其他更改。
Ruby 3主要更新
数字3在Ruby 3版本中非常有意义。它是发布版本号,使性能提高了3倍,核心贡献者(Matz,TenderLove和Koichi)也是三人组。同样,Ruby 3有3个主要目标:更快、并发性更好并确保正确性。
1. Ruby 3性能
性能是Ruby 3的主要关注重点之一。实际上,围绕Ruby 3,开发者团队内部最初就进行了讨论。Ruby创始人Matz(松本行弘)于2015年设定了一个雄心勃勃的目标,就是将Ruby的速度提高3倍。
什么是Ruby 3x3?
在讨论这一点之前,让我们重新审视Ruby的核心理念。
Matz说到:"我希望看到Ruby帮助世界上的每个程序员提高生产力,享受编程并感到幸福。"
关于Ruby 3x3,有人问是否目标是使Ruby成为最快的语言?答案是不。Ruby 3x3的主要目标是使Ruby的速度比Ruby 2快3倍。
Matz谈到:"没有一种语言足够快。"
Ruby并非为追求速度最快而设计,如果这是目标,那么Ruby将不会是今天这种局面。随着Ruby语言性能的提高,它无疑有助于我们的应用程序更快且可扩展。
Matz坦承:"在Ruby语言的设计中,我们主要集中在生产力和编程乐趣上。结果,Ruby太慢了。"
可以衡量性能的区域有两个:内存和CPU。
CPU优化
Ruby中已进行了一些增强,以提高速度。Ruby团队从以前的版本中优化了JIT(Just In Time)编译器。Ruby MJIT编译器最早是在Ruby 2.6中引入的。Ruby 3 MJIT具有更好的安全性,并且似乎在很大程度上提高了Web应用程序的性能。
MJIT的实现不同于通常的JIT。当方法被反复调用(例如10000次)时,MJIT将选择可以编译为本机代码的方法并将其放入队列。稍后MJIT将获取队列并将其转换为本地代码。
内存优化
Ruby 3带有增强的垃圾收集器。它具有类似python的缓冲区的API,有助于更好地利用内存。从Ruby 1.8开始,Ruby在垃圾回收算法方面不断进步。
自动垃圾压缩
垃圾收集的最新变化是垃圾压缩。它是在Ruby 2.7中引入的,该过程有点手动。但是在版本3中,它是全自动的,适当调用压缩程序以确保适当的内存利用率。
对象分组
垃圾压缩器移动堆中的对象。它将分散的对象组合在一起放在内存中的某个位置,以便后面更大的对象可以有效利用内存。
2. Ruby 3中的并行性和并发性
并发是任何编程语言的重要关注点之一。Matz认为Ruby程序员未能正确地使用线程这一抽象层。
Matz表示:"我很遗憾添加线程。"
Ruby 3使应用程序并发运行变得容易得多。Ruby 3中增加了一些与并发相关的功能和改进。
Fibers
在Ruby 3中,Fibers的引进被认为是突破性的。Fibers是轻量级工作线程,看起来像线程,但具有一些优势。它比线程消耗更少的内存。它为程序员提供了更大的控制权,使其可以定义可以暂停或恢复的代码段,从而实现更好的I/O处理。
Fiber Scheduler
Fiber Scheduler是Ruby 3中添加的一项实验性功能。它被引入来拦截诸如I/O之类的阻塞操作。可喜的是,它允许轻量级并发,并且可以轻松集成到现有代码库中,而无需更改原始代码逻辑。这是一个接口,可以通过诸如EventMachine或Async之类的gem创建包装器来引入,此接口设计允许事件循环实现与应用程序代码之间的关注点分离。
以下是HTTP使用并发发送多个请求的示例Async。
- require 'async'
- require 'net/http'
- require 'uri'
- LINKS = [
- 'https://xmyy.com',
- 'https://www.xmyy.com'
- ]
- Async do
- LINKS.each do |link|
- Async do
- Net::HTTP.get(URI(link))
- end
- end
- end
Ractors(Guilds)
众所周知,Ruby的globalVM lock(GVL)阻止大多数Ruby线程并行计算。Ractor可以解决此问题,GVL可以提供更好的并行性。Ractor是类似于Actor-Model的并发抽象,旨在提供并行执行而无需担心线程安全。
Ractors允许不同Ractor中的线程同时计算。每个Ractor具有至少一个线程,该线程可以包含多个Fibers。在Ractor中,在给定的时间只允许执行一个线程。
以下程序返回一个非常大的平方根。它并行计算两个数字的结果。
- # Math.sqrt(number) in ractor1, ractor2 run in parallel
- ractor1, ractor2 = *(1..2).map do
- Ractor.new do
- number = Ractor.recv
- Math.sqrt(number)
- end
- end
- # send parameters
- ractor1.send 3**71
- ractor2.send 4**51
- p ractor1.take #=> 8.665717809264115e+16
- p ractor2.take #=> 2.251799813685248e+15
3.静态分析
我们需要测试以确保我们程序的正确性。但是,从本质上讲,测试可能意味着重复的代码工作。
Matz甚至吐槽:"我讨厌测试,因为它不是人干的。"
为了确保程序的正确性,除了测试之外,静态分析是个不错的工具。
静态分析依赖于内联类型注释。解决此难题的解决方案是使.rbs文件与我们的.rb文件平行。
RBS
RBS是一种描述Ruby程序结构的语言。它为我们提供了该程序的概述,以及如何定义整体类,方法等。使用RBS,我们可以编写Ruby类、模块、方法、实例变量、变量类型和继承的定义。它支持Ruby代码中的常用模式以及高级类型(如并集和鸭子duck typing类型)。
这些.rbs文件类似于.d.tsTypeScript中的文件。以下是一个.rbs文件外观的小例子。具有类型定义的优点是可以针对实现和执行进行验证。
下面的示例是不言自明的。我们需要在这里注意的一件事是each_post接受一个块或返回一个枚举器。
- # user.rbs
- class User
- attr_reader name: String
- attr_reader email: String
- attr_reader age: Integer
- attr_reader posts: Array[Post]
- def initialize: (name: String,
- email: String,
- age: Integer) -> void
- def each_post: () { (Post) -> void } -> void
- | () -> Enumerator[Post, void]
- end
其他值得注意的变化
- 粘贴到IRB中的速度要快得多。
- 回溯的顺序已颠倒。首先打印错误消息和行号,然后打印其余的跟踪信息。
- Hash#transform_keys 接受将旧密钥与新密钥映射的哈希。
- 插值字符串文字在# frozen-string-literal: true使用时不再冻结。
- Symbol#to_proc现在返回一个lambda Proc。
- 添加了Symbol#name ,它以冻结的字符串形式返回符号的名称。
过渡
为了满足Ruby 3的目标需求,许多核心库已经作了修改。但这并不意味着我们的旧应用程序会突然停止工作。Ruby团队已确保这些更改向后兼容。我们可能会在现有代码中看到一些弃用警告。开发人员可以修复这些警告,以从旧版本平稳过渡到新版本。我们都准备使用新功能并希冀从新的性能改进中受益。
结论
随着性能、内存利用率、静态分析以及Ractors和Scheduler等新功能的极大改进,我们对Ruby的未来充满信心。使用Ruby 3,应用程序可以具有更大的可伸缩性和更令人愉快的使用。即将到来的2021年不仅是所有Rubyists的新年,而且是一个新时代。