辅助引擎缓存

以下文档介绍了 GraalVM 的辅助引擎缓存的工作原理。

此功能仅在 Oracle GraalVM 中可用。在 GraalVM Community Edition 中,这些选项不可用。

简介 #

Truffle 客户语言程序的预热可能需要大量时间。预热包括每次程序执行时重复进行的工作,直到达到峰值性能。这包括:

  1. 将客户应用程序加载并解析到 Truffle AST 数据结构中。
  2. 在解释器中执行和分析客户应用程序。
  3. 将 AST 编译为机器码。

在单个操作系统进程中,可以通过指定显式引擎来共享预热期间执行的工作。这要求语言实现禁用上下文相关的优化,以避免共享代码的上下文之间发生去优化。辅助引擎缓存基于禁用上下文相关优化的机制,并增加了将引擎及其 AST 和优化后的机器码持久化到磁盘的能力。这样,在新进程的第一个应用程序上下文中,预热期间执行的工作可以显著减少。

我们使用 SVM 辅助镜像功能来持久化和加载必要的数据结构到磁盘。持久化镜像可能需要相当长的时间,因为需要执行编译。然而,加载旨在尽可能快,通常几乎是即时的。这显著减少了应用程序的预热时间。

入门 #

从 Oracle GraalVM 安装开始,您首先需要(重新)构建一个具有辅助引擎缓存功能的镜像。例如,可以通过添加辅助引擎缓存功能来重新构建 JavaScript 镜像:

graalvm/bin/native-image --macro:js-launcher -H:+AuxiliaryEngineCache -H:ReservedAuxiliaryImageBytes=1073741824

--macro 参数值取决于客户语言。默认情况下,辅助镜像最大可达 1GB。最大大小可以根据需要增加或减少。保留的字节数实际上不会影响应用程序消耗的内存。在未来的版本中,当使用 --macro:js-launcher 宏时,辅助引擎缓存将默认启用。

重新构建 JavaScript 启动器后,该功能的使用方式如下:

创建一个新文件 fib.js

function fib(n) {
   if (n == 1 || n == 2) {
       return 1;
   }
   return fib(n - 1) + fib(n - 2);
}
console.log(fib(32))

为了将分析运行的引擎持久化到磁盘,请使用以下命令行:

graalvm/bin/js --experimental-options --engine.TraceCache=true --engine.CacheStore=fib.image fib.js

--engine.TraceCache=true 选项是可选的,它允许您查看正在发生的事情。

输出如下:

[engine] [cache] No load engine cache configured.
2178309
[engine] [cache] Preparing engine for store (compile policy hot)...
[engine] [cache] Force compile targets mode: hot
[engine] [cache] Prepared engine in 1 ms.
[engine] [cache] Persisting engine for store ...
[engine] [cache] Persisted engine in 20 ms.
[engine] [cache] Detecting changes (update policy always)...
[engine] [cache]     New image contains         1 sources and  82 function roots.
[engine] [cache]     Always persist policy.
[engine] [cache] Writing image to fib.image...
[engine] [cache] Finished writing 1,871,872 bytes in 4 ms.

现在可以使用以下命令从磁盘加载引擎:

graalvm/bin/js --experimental-options --engine.TraceCache --engine.CacheLoad=fib.image fib.js

它会打印:

[engine] [cache] Try loading image './fib.image'...
[engine] [cache] Loaded image in 0 ms. 1,871,872 bytes   1 sources  82 roots
[engine] [cache] Engine from image successfully patched with new options.
2178309
[engine] [cache] No store engine cache configured.

由于无需预热应用程序,应用程序的执行时间应显著缩短。

用法 #

缓存存储和加载操作可以使用以下选项进行控制:

  • --engine.Cache=<path> 从/向 path 加载和存储缓存的引擎。
  • --engine.CacheStore=<path> 将缓存的引擎存储到 path
  • --engine.CacheLoad=<path>path 加载缓存的引擎。
  • --engine.CachePreinitializeContext=<boolean> 在镜像中预初始化一个新的上下文(默认 true)。
  • --engine.TraceCache=<boolean> 启用调试输出。
  • --engine.TraceCompilation=<boolean> 打印强制编译。

当使用 --engine.CacheCompile=<policy> 选项存储镜像时,可以强制编译根。支持的策略有:

  • none: 不会持久化任何编译,并且现有编译将失效。
  • compiled: 不会强制编译,但已完成的编译将被持久化。
  • hot: 所有已启动的编译都将完成,然后持久化。(默认)
  • aot: 所有已启动和 AOT 可编译的根都将被强制编译并持久化。
  • executed: 所有已执行和所有 AOT 可编译的根都将被强制编译。

默认情况下,编译队列中所有已启动的编译都将完成,然后持久化。函数根是否可 AOT 编译由语言决定。语言通过实现 RootNode.prepareForAOT() 来支持 AOT。

如果同时设置了加载和存储操作,则可以使用 --engine.UpdatePolicy=<policy> 选项指定更新策略。可用策略有:

  • always 始终持久化。
  • newsource 如果加载了先前加载的镜像中不包含的新源,则存储。
  • newroot 如果加载了先前加载的镜像中不包含的新根,则存储。
  • never 从不持久化。

已知限制 #

  • 对可持久化的应用程序类型通常没有限制。如果语言支持共享上下文策略,辅助引擎缓存应该可以工作。如果语言不支持,则不会持久化任何数据。

  • 持久化的辅助引擎镜像只能与创建它的 SVM 原生镜像一起使用。将引擎镜像与任何其他原生镜像一起使用将失败。

  • 每个原生镜像隔离区只能有一个活动的辅助镜像。同时尝试加载多个辅助镜像将失败。目前,辅助镜像也无法卸载,但计划在未来解除此限制。

安全考虑 #

所有持久化到磁盘的数据仅表示代码,不包含应用程序上下文特定的数据,例如全局变量。但是,分析过的 AST 和代码可能包含 Truffle AST 中执行的优化产生的工件。例如,运行时字符串可能用于优化并因此持久化到引擎镜像中。

NativeImage 上的开发和调试 #

在 NativeImage 上运行辅助引擎缓存时,有几个选项对调试很有用:

  • -XX:+TraceAuxiliaryImageClassHistogram 持久化时打印镜像中所有对象的类直方图。
  • -XX:+TraceAuxiliaryImageReferenceTree 持久化时打印镜像中所有对象的类引用树。

HotSpot 上的开发和调试 #

调试 HotSpot 上与辅助镜像相关的语言实现问题可能很有用。在 JVM 模式下的 Oracle GraalVM 中,我们有额外的选项可用于帮助调试此功能的问题:由于 HotSpot 不支持存储部分堆,因此这些调试功能在 HotSpot 上不起作用。

  • --engine.DebugCacheStore=<boolean> 准备引擎进行缓存并将其存储到静态字段,而不是写入磁盘。
  • --engine.DebugCacheLoad=<boolean> 准备引擎使用存储在静态字段中的引擎,而不是从磁盘读取。
  • --engine.DebugCacheCompile=<boolean> 用于在持久化引擎之前强制编译已执行调用目标的策略。这支持与 --engine.CacheCompile 相同的值。
  • --engine.DebugCacheTrace=<boolean> 启用引擎缓存调试功能的跟踪。

例如

js --experimental-options --engine.TraceCompilation --engine.DebugCacheTrace --engine.DebugCacheStore --engine.DebugCacheCompile=executed fib.js

打印以下输出:

[engine] opt done         fib                                                         |ASTSize            32 |Time   231( 147+84  )ms |Tier             Last |DirectCallNodes I    6/D    8 |GraalNodes   980/ 1857 |CodeSize         7611 |CodeAddress 0x10e20e650 |Source       fib.js:2
2178309
[engine] [cache] Preparing debug engine for storage...
[engine] [cache] Force compile targets mode: executed
[engine] [cache] Force compiling 4 roots for engine caching.
[engine] opt done         @72fa3b00                                                   |ASTSize             3 |Time   211( 166+45  )ms |Tier             Last |DirectCallNodes I    2/D    1 |GraalNodes   500/ 1435 |CodeSize         4658 |CodeAddress 0x10e26c8d0 |Source            n/a
[engine] opt done         :program                                                    |ASTSize            25 |Time   162( 123+39  )ms |Tier             Last |DirectCallNodes I    1/D    1 |GraalNodes   396/ 1344 |CodeSize         4407 |CodeAddress 0x10e27fd50 |Source       fib.js:1
[engine] opt done         Console.log                                                 |ASTSize             3 |Time    26(  11+15  )ms |Tier             Last |DirectCallNodes I    0/D    0 |GraalNodes    98/  766 |CodeSize         2438 |CodeAddress 0x10e285710 |Source    <builtin>:1
[engine] [cache] Stored debug engine in memory.

这允许快速迭代与编译相关的问题,并附加 Java 调试器。可以使用 --vm.Xdebug --vm.Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 附加 Java 调试器。

调试持久化引擎的加载更困难,因为 HotSpot 不支持将引擎写入磁盘。但是,可以使用多语言嵌入 API 在单元测试中模拟此用例。请参阅 com.oracle.truffle.enterprise.test.DebugEngineCacheTest 类作为示例。

联系我们