辅助引擎缓存

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

此功能仅在 Oracle GraalVM 中可用。在 GraalVM 社区版中,这些选项不可用。

简介 #

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 上调试与辅助镜像相关的语言实现问题可能很有用。在 Oracle GraalVM 的 JVM 模式下,我们有额外的选项可以用于帮助调试此功能出现的问题:由于在 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 上不支持将引擎写入磁盘。但是,可以使用 Polyglot 嵌入式 API 在单元测试中模拟此用例。请参见 com.oracle.truffle.enterprise.test.DebugEngineCacheTest 类作为示例。

与我们联系