- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
兼容性
TruffleRuby旨在完全兼容Ruby的标准实现(MRI)3.3.5版本,包括C扩展。TruffleRuby仍在开发中,因此尚未完全兼容。
TruffleRuby可以运行Rails,并与许多gem兼容,包括C扩展。TruffleRuby通过了大约97%的ruby/spec,超过了任何其他替代Ruby实现。
任何与MRI不兼容之处都被视为一个bug,除了下文详述的少数情况。如果您发现与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不同 #
大多数fiber的使用场景依赖于它们易于启动、开销低且内存开销小。在TruffleRuby中,fiber目前使用操作系统线程实现,因此它们具有与Ruby线程相同的性能特性。一旦Loom项目稳定并在JVM发布版中可用,此问题将得到解决。
某些标记为内部的类会有所不同 #
MRI提供了一些在文档中描述为仅在MRI (CRuby) 上可用的类。如果可行,则会实现这些类,但并非总是如此。例如,RubyVM
不可用。
Regexp
#
在TruffleRuby中,Regexp
实例始终是不可变的。在CRuby 3.1中,所有字面量Regexp
都是不可变的,但非字面量仍然是可变的。此限制意味着无法在Regexp实例上定义单例方法,也无法在TruffleRuby上创建Regexp子类的实例。
存在细微差异的特性 #
命令行开关 #
-y
、--yydebug
、--dump=
和--debug-frozen-string-literal
开关会被忽略并发出警告,因为它们是不受支持的开发工具。
带有魔术注释(magic-comments)并通过-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
选项使用Polyglot引擎提供的标准I/O流,对文件描述符0、1和2的读写将重定向到这些流。这意味着在这些文件描述符上的其他I/O操作(例如isatty
)可能与这些流的实际目的地无关,并且像dup
这样的操作可能会丢失与多语言流的连接。例如,如果像某些日志框架那样重新打开$stdout
,您将获得原生标准输出,而不是多语言输出。
此外,I/O缓冲区刷新、sync
设置的I/O对象的写入以及write_nonblock
不会因为EAGAIN
和EWOULDBLOCK
而重试写入,因为这些流没有提供检测此情况的方法。
错误消息 #
错误消息字符串有时会与MRI不同,因为这些通常不在Ruby Spec Suite或测试的覆盖范围内。
信号 #
首先,根据POSIX(man 2 signal
),KILL
和STOP
永远不能被捕获。某些信号在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()
检查宏是否存在)。所有这些问题都应被视为bug并加以修复。请报告这些情况。
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的互操作 #
通过GraalVM Polyglot API支持从Java调用Ruby代码。
Java扩展 #
不支持使用为JRuby编写的Java扩展。
原生配置中尚不支持的特性 #
在原生配置中运行TruffleRuby与在JVM上运行基本相同。资源管理方面存在差异,因为两个VM使用不同的垃圾回收器,但从功能上讲,它们基本处于同等水平。
原生配置下的Java互操作性 #
Java互操作性在原生配置中可用,但需要更多设置。默认情况下,只有部分数组类在镜像中可用于Java互操作。您可以通过编译包含TruffleRuby的原生镜像来添加更多类。详情请参阅此处。
规范完整性 #
“有多少个规范?”这个问题没有一个简单、精确的答案。规范的数量因Ruby语言的不同版本、不同平台和规范的不同版本而异。标准库和C扩展API的规范也非常不平衡,可能会给出误导性的结果。
这篇博客文章总结了TruffleRuby通过了多少个规范。