對於所有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個主要目標:更快、併發性更好並確保正確性。
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 SchedulerFiber 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檔案平行。
RBSRBS是一種描述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的新年,而且是一個新時代。