- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
- Truffle 语言实现框架
- Truffle 分支插桩
- 动态对象模型
- 静态对象模型
- 解释器代码的主机优化
- Truffle 函数内联方法
- 分析 Truffle 解释器
- Truffle 互操作 2.0
- 语言实现
- 使用 Truffle 实现新语言
- Truffle 语言和工具迁移到 Java 模块
- Truffle 原生函数接口
- 优化 Truffle 解释器
- 选项
- 栈上替换
- Truffle 字符串指南
- 特化直方图
- 测试 DSL 特化
- 基于多语言 API 的 TCK
- Truffle 编译队列方法
- Truffle 库指南
- Truffle AOT 概述
- Truffle AOT 编译
- 辅助引擎缓存
- Truffle 语言安全点教程
- 单态化
- 拆分算法
- 单态化用例
- 向运行时报告多态特化
辅助引擎缓存
以下文档介绍了 GraalVM 的辅助引擎缓存的工作原理。
此功能仅在 Oracle GraalVM 中可用。在 GraalVM Community Edition 中,这些选项不可用。
简介 #
Truffle 客户语言程序的预热可能需要大量时间。预热包括每次程序执行时重复进行的工作,直到达到峰值性能。这包括:
- 将客户应用程序加载并解析到 Truffle AST 数据结构中。
- 在解释器中执行和分析客户应用程序。
- 将 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
类作为示例。