Truffle AOT 概述

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

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

第一个上下文的预初始化 #

本机镜像允许在镜像构建时运行 Java 代码中的静态初始化器。在运行静态初始化后,从静态字段引用的值将被快照并持久化到镜像中。上下文预初始化利用此功能,在镜像构建时创建和初始化语言上下文,以便在运行时由隔离或进程中创建的第一个上下文使用。这通常会显着提高第一个上下文的初始化时间。

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

有关更多信息,请参阅 TruffleLanguage.patchContext javadoc。

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

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

支持上下文无关代码 #

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

Truffle 框架通过调用 TruffleLanguage.initializeMultipleContexts 来宣布可能在多个上下文使用语言实例,通常甚至是在第一个上下文创建之前。当使用显式引擎或 --engine.CacheStore 设置为 true 时,框架能够在第一个上下文创建之前初始化多个上下文。

在支持上下文无关代码时,应满足以下条件

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

预计为多个上下文创建的 AST 将被编译成效率较低的机器代码,因为它不允许对运行时值的标识进行推测。例如,不能推测内联缓存中的函数实例,而必须推测所包含的 CallTarget。这比较慢,因为它需要额外的读取操作来访问存储在函数中的 CallTarget。创建上下文无关代码可能很昂贵,因此,如果未初始化多个上下文,则仍应推测运行时值。

SimpleLanguageJavaScript 是两种已经支持上下文无关代码的语言,可能有助于指导解决具体问题。

使用辅助引擎缓存的持久化上下文无关代码(Oracle GraalVM) #

Oracle GraalVM 支持将代码数据结构持久化到磁盘。这使得能够几乎消除隔离/进程中应用程序第一次运行的预热时间。SVM 辅助镜像功能用于将必要的数据结构持久化到磁盘以及从磁盘加载。持久化镜像可能需要很长时间,因为需要执行编译。但是,加载设计得尽可能快,通常几乎是瞬时的。

即使上下文是在没有显式引擎的情况下创建的,引擎缓存也是通过选项和功能启用的。

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

不进行分析的编译 #

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

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

应用程序快照 #

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

联系我们