- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (抢先体验)
- GraalVM for JDK 21
- GraalVM for JDK 17
- 存档
- 开发版本
兼容性
TruffleRuby 旨在与标准 Ruby 实现(MRI)版本 3.2.2 完全兼容,包括 C 扩展。 TruffleRuby 仍在开发中,因此它尚未完全兼容。
TruffleRuby 可以运行 Rails,并且与许多 gems 兼容,包括 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_DATE
是git
提交日期。RUBY_PATCHLEVEL
始终为零。RUBY_ENGINE_VERSION
是 TruffleRuby 版本,或者如果你的构建不是 TruffleRuby 版本的一部分,则为0.0-
和 Git 提交哈希值。
在 C API 中,定义了预处理器宏 TRUFFLERUBY
,可以使用 #ifdef TRUFFLERUBY
检查它。
Ruby 3.x 功能 #
TruffleRuby 支持 Ruby 3.2 及更早版本的大多数功能。 但是,某些功能尚未实现。 有关详细信息,请参阅以下问题
完全缺少的功能 #
延续和 callcc
#
延续在 MRI 中已过时,建议使用 Fibers 替代。 延续和 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
#
Regexp
实例在 TruffleRuby 中始终是不可变的。 在 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 流 #
如果你使用通过实验性的 --polyglot-stdio
选项提供的多语言引擎提供的标准 I/O 流,则对文件描述符 0、1 和 2 的读写将被重定向到这些流。 这意味着,对这些文件描述符的其他 I/O 操作(如 isatty
)可能与这些流的实际结束位置不相关,并且 dup
等操作可能会失去与多语言流的连接。 例如,如果你使用 $stdout.reopen
(正如某些日志记录框架所做的那样),你将获得原生标准输出,而不是多语言输出。
此外,I/O 缓冲区排水、对 sync
设置为 true
的 I/O 对象的写入以及 write_nonblock
不会在 EAGAIN
和 EWOULDBLOCK
上重试写入,因为流没有提供检测此问题的方法。
错误消息 #
错误消息字符串有时会与 MRI 不同,因为这些通常不受 Ruby Spec 套件 或测试的覆盖。
信号 #
首先,KILL
和 STOP
永远无法被捕获,这是 POSIX 的规定(man 2 signal
)。 一些信号在 CRuby 上是保留的,它们在 TruffleRuby 上也是保留的,因为捕获它们会导致各种问题:SEGV
、BUS
、ILL
、FPE
和 VTALRM
。
在使用原生配置时,TruffleRuby 允许捕获与 MRI 相同的所有信号。 因此,在原生配置中,任何在 MRI 上运行的信号处理代码都可以在 TruffleRuby 上运行而无需修改。
但是,在 JVM 上运行时,TruffleRuby 无法捕获 QUIT
,因为此信号被 JVM 保留。 在这种情况下,trap(:QUIT) {}
将引发 ArgumentError
。 任何依赖于能够捕获此信号的代码都需要回退到另一个可用的信号。
当 TruffleRuby 作为多语言应用程序的一部分运行时,任何由另一种语言处理的信号都将无法用于 TruffleRuby 捕获。
GC 统计信息 #
TruffleRuby 提供了与 MRI 相似的 GC.stat
统计信息,但并非所有统计信息都可用,并且某些统计信息可能是近似值。 使用 GC.stat.keys
查看哪些提供了真实或近似值。 缺少的值将返回 0
。
调用者位置 #
使用 Kernel#caller_locations
或 Thread.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 个参数。
RDATA
和 RTYPEDDATA
的 mark
函数 #
RDATA
和 RTYPEDDATA
的 mark
函数在垃圾回收期间不会被调用,而是在定期调用。有关对象的信息在它们被分配到结构体时被缓存,并且 TruffleRuby 在缓存已满时定期运行所有 mark
函数,以以垃圾收集器可以理解的方式表示这些对象关系。该过程应该与 MRI 相同。
与 JRuby 的兼容性 #
Ruby 到 Java 的互操作性 #
TruffleRuby 不支持与 JRuby 相同的 Java 互操作性接口。TruffleRuby 提供了一个替代的多语言 API,用于与多种语言(包括 Java)进行互操作。
Java 到 Ruby 的互操作性 #
从 Java 调用 Ruby 代码由 GraalVM 多语言 API 支持。
Java 扩展 #
不支持使用为 JRuby 编写的 Java 扩展。
原生配置中尚未支持的功能 #
在原生配置中运行 TruffleRuby 与在 JVM 上运行基本相同。在资源管理方面存在差异,因为这两个虚拟机使用不同的垃圾收集器,但在功能方面,它们基本相当。
原生配置中的 Java 互操作性 #
Java 互操作性在原生配置中有效,但需要更多设置。默认情况下,只有某些数组类在 Java 互操作性的镜像中可用。可以通过编译包括 TruffleRuby 的原生镜像来添加更多类。有关更多详细信息,请参阅此处。
规范完整性 #
“有多少规范?”这个问题没有一个简单而精确的答案。规范的数量因 Ruby 语言的不同版本、不同平台以及规范的不同版本而异。标准库和 C 扩展 API 的规范也非常不均匀,可能会给出误导性的结果。
这篇博文总结了 TruffleRuby 通过了多少规范。