Experimental feature in 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_DATEgit提交日期。
  • 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不会因为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()检查宏是否存在)。所有这些问题都应被视为bug并加以修复。请报告这些情况。

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的互操作 #

通过GraalVM Polyglot API支持从Java调用Ruby代码。

Java扩展 #

不支持使用为JRuby编写的Java扩展。

原生配置中尚不支持的特性 #

在原生配置中运行TruffleRuby与在JVM上运行基本相同。资源管理方面存在差异,因为两个VM使用不同的垃圾回收器,但从功能上讲,它们基本处于同等水平。

原生配置下的Java互操作性 #

Java互操作性在原生配置中可用,但需要更多设置。默认情况下,只有部分数组类在镜像中可用于Java互操作。您可以通过编译包含TruffleRuby的原生镜像来添加更多类。详情请参阅此处

规范完整性 #

“有多少个规范?”这个问题没有一个简单、精确的答案。规范的数量因Ruby语言的不同版本、不同平台和规范的不同版本而异。标准库和C扩展API的规范也非常不平衡,可能会给出误导性的结果。

这篇博客文章总结了TruffleRuby通过了多少个规范。

联系我们