Truffle AOT 概述

Truffle 支持多种不同形式的 AOT 预初始化、编译和缓存。本文旨在概述这些功能。

请注意,此处提及的某些功能仅在 Oracle GraalVM 中受支持。

首次上下文的预初始化 #

Native image 允许在镜像构建时在静态初始化器中运行 Java 代码。静态初始化运行后,从静态字段引用的值会被快照并持久化到镜像中。上下文预初始化利用此功能,在镜像构建时创建并初始化语言上下文,供运行时在隔离区或进程中创建的第一个上下文使用。这通常会显著改善第一个上下文的初始化时间。

通过在镜像构建时设置系统属性 -Dpolyglot.image-build-time.PreinitializeContexts=ruby,llvm 可以启用上下文预初始化。语言需要实现 TruffleLanguage.patchContext 并返回 true 以支持上下文预初始化。此外,语言需要注意不要绑定任何主机特定数据或创建不允许存储在 Native image 中的对象,例如 java.lang.Thread 实例。

更多信息请参阅 TruffleLanguage.patchContext 的 Javadoc。

同一隔离区/进程内的代码共享 #

可以使用多语言引擎来确定上下文之间的代码共享范围。可以在参考手册中找到具体示例。当为多语言上下文初始化语言时,会从引擎请求一个新的语言实例。如果语言支持 ContextPolicy.SHARED,则该语言实例将对一个引擎实例进行复用。源解析缓存与语言实例关联,因此每个语言实例只进行一次解析。语言可以通过实现 TruffleLanguage.areOptionsCompatible 来选择不允许将语言实例复用于新的额外上下文。这允许语言假定特定上下文选项对该语言创建的所有根节点都是编译最终的。此规则的一个例外是 InteropLibrary,其中节点可以在语言实例之间无条件共享。

支持上下文独立代码 #

代码共享要求所有代码数据结构都独立于其上下文。例如,如果代码可以在一个上下文下执行,然后在新上下文下再次执行而无需去优化,则该代码是上下文独立的。验证语言实现上下文独立性的一个好方法是:使用显式引擎创建上下文,运行测试应用程序,然后验证第二个上下文在运行相同的确定性应用程序时不会导致去优化。

Truffle 框架通过调用 TruffleLanguage.initializeMultipleContexts 来声明语言实例在多个上下文中的潜在使用,这通常甚至在创建第一个上下文之前就进行。当使用显式引擎或将 --engine.CacheStore 设置为 true 时,框架能够在创建第一个上下文之前初始化多个上下文。

支持上下文独立代码时应满足以下条件:

  • 初始化多个上下文时,必须禁用所有关于运行时值标识的推测,因为它们在与第二个上下文一起使用时将导致必然的去优化。
  • 函数内联缓存应修改为两级内联缓存实现。第一级推测函数实例的标识,第二级推测底层 CallTarget 实例。如果初始化了多个上下文,则必须禁用第一级缓存,因为这会不必要地导致去优化。
  • DynamicObject 根 Shape 实例应存储在语言实例中,而不是语言上下文。否则,任何对形状的内联缓存都不会稳定,最终会进入通用状态。
  • 所有节点实现都不得存储依赖于上下文的数据结构或依赖于上下文的运行时值。
  • 即使对于语言内部的内置函数,源的加载和解析也应使用 TruffleLanguage.Env.parse 进行,以便按语言实例缓存源解析。
  • 所有假设实例应存储在语言实例中,而不是上下文中。在初始化了多个上下文的情况下,使用上下文引用读取的上下文实例可能不再是常量。在这种情况下,从上下文读取的任何假设都不会被折叠,并且会导致显著的运行时性能开销。来自语言的假设可以在单上下文和多上下文模式下由编译器折叠。

由于不允许推测运行时值的标识,为多个上下文创建的 AST 预计会编译成效率较低的机器代码。例如,在内联缓存中,不再推测函数实例,而是需要推测所包含的 CallTarget。这会更慢,因为它需要额外的读取来访问存储在函数中的 CallTarget。创建上下文独立代码可能代价高昂,因此,如果未初始化多个上下文,仍应进行运行时值推测。

SimpleLanguageJavaScript 是两种已经支持上下文独立代码的语言,可作为解决具体问题的参考。

持久化上下文独立代码与辅助引擎缓存(Oracle GraalVM) #

Oracle GraalVM 支持将代码数据结构持久化到磁盘。这几乎可以消除应用程序在隔离区/进程中首次运行的预热时间。SVM 辅助镜像功能用于将必要的数据结构持久化并加载到磁盘。持久化镜像可能需要大量时间,因为需要进行编译。然而,加载旨在尽可能快,通常几乎是即时的。

即使上下文是在没有显式引擎的情况下创建的,引擎缓存也可以通过选项启用并正常工作。

有关引擎缓存的更多信息,请参阅引擎缓存教程

无剖析编译 #

默认情况下,如果语言函数被创建但从未执行,则在存储到辅助引擎缓存镜像中时不会被编译。辅助引擎缓存支持触发对已加载但从未执行的根节点的编译。在这种情况下,框架会调用 RootNode.prepareForAOT 方法。

有关使语言实现准备好在未先行执行的情况下进行编译的更多信息,请参阅 AOT 教程。请注意,并非所有语言都可以在未先行执行的情况下进行编译并生成高效的机器代码。静态类型语言通常更适合此目的。

应用程序快照 #

还计划支持将多语言上下文实例的运行时值持久化到磁盘。一旦此功能实现,此处将提供更多信息。

联系我们