Experimental feature in GraalVM

相容性

TruffleRuby 的目標是與 Ruby 的標準實作 MRI 版本 3.2.2 完全相容,包含 C 擴充功能。TruffleRuby 仍在開發中,因此尚未達到 100% 相容。

TruffleRuby 可以執行 Rails,並與許多 gem 相容,包括 C 擴充功能。TruffleRuby 通過了大約 97% 的 ruby/spec,比任何其他替代的 Ruby 實作都多。

任何與 MRI 的不相容都被視為錯誤,除了以下詳述的少數情況。如果您發現與 MRI 不相容的地方,請回報

TruffleRuby 盡可能地與 MRI 的行為相符。在少數情況下,TruffleRuby 會刻意與 MRI 不相容,以便提供更大的功能。

識別 #

TruffleRuby 定義了這些常數用於識別

  • RUBY_ENGINE'truffleruby'
  • RUBY_VERSION 是相容的 MRI 版本。
  • RUBY_REVISION 是用來建置 TruffleRuby 的完整 git 提交雜湊值(類似於 MRI 2.7+)。
  • RUBY_RELEASE_DATEgit 提交日期。
  • RUBY_PATCHLEVEL 永遠為零。
  • RUBY_ENGINE_VERSION 是 TruffleRuby 版本,或者,如果您的建置不是 TruffleRuby 發行版的一部分,則為 0.0- 和 Git 提交雜湊值。

在 C API 中,定義了預處理器巨集 TRUFFLERUBY,可以使用 #ifdef TRUFFLERUBY 檢查。

Ruby 3.x 功能 #

TruffleRuby 支援 Ruby 3.2 及更早版本的大部分功能。但是,某些功能尚未實作。請參閱以下問題以了解詳細資訊

完全缺失的功能 #

Continuations 和 callcc #

Continuations 在 MRI 中已過時,建議改用 Fibers。Continuations 和 callcc 不太可能在 TruffleRuby 中實作,因為它們的語義根本不符合 JVM 架構。

Fork #

您不能 fork TruffleRuby 解譯器。當在 JVM 上執行時,此功能不太可能被支援,但未來可能會在原生組態中支援。測試 fork 是否可用的正確且可移植的方法是

Process.respond_to?(:fork)

標準函式庫 #

以下標準函式庫不受支援

  • continuation:在 MRI 中已過時
  • debug:它依賴於 RubyVM::InstructionSequence,請改用 VSCode 擴充功能--inspect
  • io/console:部分實作
  • io/wait:部分實作
  • pty:未來可能會實作

TruffleRuby 為 ffi gem 提供自己的後端實作,類似於 JRuby。這應該是完全透明的,並且行為與在 MRI 上相同。此實作應該相當完整,並且通過了 ffi gem 的所有規格,除了某些很少使用的邊緣情況。

內部 MRI 功能 #

RubyVM 不適用於使用者,並且未實作。

具有主要差異的功能 #

執行緒平行執行 #

在 MRI 中,執行緒是並行排程,但不是平行執行。在 TruffleRuby 中,執行緒是平行排程的。如同 JRuby 和 Rubinius,您有責任正確地同步對您自己的共享可變資料結構的存取,而 TruffleRuby 將負責正確地同步解譯器的狀態。

Fibers 不具有與 MRI 中相同的效能特性 #

Fibers 的大多數使用案例都依賴於它們易於且廉價地啟動,並且具有較低的記憶體開銷。在 TruffleRuby 中,Fibers 目前使用作業系統執行緒實作,因此它們具有與 Ruby 執行緒相同的效能特性。一旦 Loom 專案在 JVM 版本中變得穩定且可用,將會解決這個問題

某些標示為內部的類別將會有所不同 #

MRI 提供一些在文件中描述為僅在 MRI (CRuby) 上可用的類別。如果實際可行,這些類別將會被實作,但情況並非總是如此。例如,RubyVM 不可用。

Regexp #

在 TruffleRuby 中,Regexp 實例永遠是不可變的。在 CRuby 3.1 中,所有字面 Regexp 都是不可變的,但非字面仍然是可變的。此限制表示無法在 Regexp 實例上定義單例方法,並且無法在 TruffleRuby 上建立 Regexp 子類的實例。

具有細微差異的功能 #

命令列開關 #

-y--yydebug--dump=--debug-frozen-string-literal 開關將被忽略並發出警告,因為它們是不受支援的開發工具。

以魔術註解傳遞在 -e 引數中的程式必須具有 UTF-8 或 UTF-8 子集的編碼,因為 JVM 在我們取得它們時已經解碼了引數。

--jit 選項和 jit 功能對 TruffleRuby 沒有影響,並會發出警告。當可用時,始終會使用 GraalVM 編譯器。

字串的最大位元組大小為 231-1 #

Ruby 字串表示為 Java byte[]。JVM 強制執行 231-1 的最大陣列大小(透過將大小儲存在 32 位元帶正負號的 int 中),因此 Ruby 字串不能超過 231-1 個位元組。也就是說,字串必須小於 2GB。這與 JRuby 的限制相同。一個可能的解決方法可能是使用原生配置的字串,但要支援原生字串上的每個 Ruby 字串操作將需要很大的努力。

UTF-16 和 UTF-32 編碼的字串 #

TruffleRuby 不支援具有奇數位元組數(以原生位元組序)的 UTF-16 字串。同樣地,對於 UTF-32,它必須是 4 的倍數。這對於最佳化、壓縮、不變性等是必要的。

執行緒在不同點偵測中斷 #

與在 MRI 上相比,TruffleRuby 執行緒可能會在程式中的不同點偵測到它們已中斷。一般來說,TruffleRuby 似乎比 MRI 更快地偵測到中斷。JRuby 和 Rubinius 也與 MRI 不同;該行為未在 MRI 中記錄,並且可能會在 MRI 版本之間變更,因此不建議依賴中斷點。

多語言標準 I/O 串流 #

如果您使用多語言引擎提供的標準 I/O 串流,透過實驗性的 --polyglot-stdio 選項,則對檔案描述符 0、1 和 2 的讀寫將會重新導向到這些串流。這表示這些檔案描述符上的其他 I/O 操作(例如 isatty)可能與這些串流實際結束的位置無關,而 dup 等操作可能會遺失與多語言串流的連線。例如,如果您 $stdout.reopen,就像某些記錄框架所做的那樣,您將會取得原生標準輸出,而不是多語言輸出。

此外,I/O 緩衝區清空、在 sync 設定的 I/O 物件上的寫入,以及 write_nonblock 將不會在 EAGAINEWOULDBLOCK 上重試寫入,因為串流沒有提供偵測此情況的方法。

錯誤訊息 #

錯誤訊息字串有時會與 MRI 不同,因為這些通常不在 Ruby Spec Suite 或測試的範圍內。

訊號 #

首先,根據 POSIX (man 2 signal),永遠無法捕獲 KILLSTOP。某些訊號在 CRuby 上是保留的,它們在 TruffleRuby 上也是保留的,因為捕獲這些訊號會導致各種問題:SEGVBUSILLFPEVTALRM

當使用原生配置時,TruffleRuby 允許捕獲所有 MRI 能捕獲的相同訊號。因此,任何在 MRI 上執行的訊號處理程式碼,在原生配置下都能在 TruffleRuby 上無需修改地執行。

然而,當在 JVM 上執行時,TruffleRuby 無法捕獲 QUIT 訊號,因為此訊號已由 JVM 保留。在這種情況下,trap(:QUIT) {} 將會引發 ArgumentError。任何依賴捕獲此訊號的程式碼,都需要回退到另一個可用的訊號。

當 TruffleRuby 作為多語言應用程式的一部分執行時,任何由其他語言處理的訊號,TruffleRuby 都無法捕獲。

GC 統計資訊 #

TruffleRuby 提供類似於 MRI 的 GC.stat 統計資訊,但並非所有統計資訊都可用,且某些統計資訊可能是近似值。使用 GC.stat.keys 來查看哪些提供了實際或近似值。遺失的值將會回傳 0

呼叫者位置 #

使用 Kernel#caller_locationsThread.each_caller_location 可能會包含引擎特定的位置物件和/或路徑。這是預期的行為,必要時應在應用程式碼中進行過濾。

Thread.to_enum(:each_caller_location) 返回的列舉器不支援使用 .next 進行迭代。在 CRuby 中,這會引發 StopIteration,而在 TruffleRuby 中,它會在未確定的 (與 .next 被呼叫的位置和方式有關) 呼叫堆疊上迭代。不建議在任何情況下使用此功能(無論是 CRuby 還是 TruffleRuby)。

效能極低的特性 #

ObjectSpace #

ObjectSpace#each_object 已實作,但速度相當慢,因為需要迭代整個堆積,且本質上執行與 GC 標記階段等效的操作。 ObjectSpace#trace_object_allocations_start 會降低所有配置的速度,與 CRuby 上的行為類似。使用 ObjectSpace 上的大多數方法會暫時降低您程式的效能。在測試案例和其他類似的「離線」操作中使用它們是可以的,但您可能不希望在生產應用程式的內部迴圈中使用它們。

set_trace_func #

使用 set_trace_func 將會暫時降低您程式的效能。如同 ObjectSpace,建議您不要在生產應用程式的內部迴圈中使用此功能。

回溯追蹤 #

拋出例外和其他需要建立回溯追蹤的操作通常比在 MRI 上慢。這是因為 TruffleRuby 需要撤銷已應用於快速執行 Ruby 程式碼的優化,以便重新建立回溯追蹤條目。無論如何,不建議在任何 Ruby 實作中使用例外來控制流程。

為了幫助減輕這個問題,當我們偵測到回溯追蹤不會被使用時,它們會自動被停用。

C 擴充相容性 #

識別符號可能是巨集或函式 #

通常是巨集的識別符號可能是函式,函式可能是巨集,全域變數可能是巨集。這可能會在使用它們的上下文中造成問題,因為該上下文依賴於特定的實作(例如,取得其位址、指定給函式指標變數,以及使用 defined() 來檢查巨集是否存在)。這些問題都應被視為錯誤並加以修復。請回報這些情況。

rb_scan_args #

rb_scan_args 僅支援最多 10 個指標。

rb_funcall #

rb_funcall 僅支援最多 15 個引數。

RDATARTYPEDDATAmark 函式 #

RDATARTYPEDDATAmark 函式不會在垃圾收集期間被呼叫,而是定期被呼叫。當物件被指定給結構時,其相關資訊會被快取,而當快取已滿時,TruffleRuby 會定期執行所有 mark 函式,以便以垃圾收集器可以理解的方式來表示這些物件之間的關係。此過程的行為應與 MRI 完全相同。

與 JRuby 的相容性 #

Ruby 與 Java 的互通性 #

TruffleRuby 不支援與 JRuby 相同的 Java 互通性介面。TruffleRuby 提供一個替代的多語言 API,用於與包括 Java 在內的多種語言進行互通。

Java 與 Ruby 的互通性 #

支援從 Java 呼叫 Ruby 程式碼,請使用 GraalVM 多語言 API

Java 擴充 #

不支援使用為 JRuby 撰寫的 Java 擴充。

原生配置中尚未支援的功能 #

在原生配置中執行 TruffleRuby 與在 JVM 上執行大致相同。在資源管理方面存在差異,因為這兩種 VM 使用不同的垃圾收集器,但在功能上,它們基本上是相同的。

原生配置中的 Java 互通性 #

Java 互通性在原生配置中有效,但需要更多設定。預設情況下,只有一些陣列類別可用於映像中的 Java 互通性。您可以透過編譯包含 TruffleRuby 的原生映像來新增更多類別。詳情請參閱此處

規格完整性 #

「有多少規格?」並不是一個容易且準確回答的問題。規格的數量因 Ruby 語言的不同版本、不同的平台和規格的不同版本而異。標準程式庫和 C 擴充 API 的規格也非常不均勻,可能會產生誤導性的結果。

這篇部落格文章總結了 TruffleRuby 通過了多少規格。

與我們聯繫