- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
配置文件引导优化
什么是配置文件引导优化? #
即时 (JIT) 编译器相对于预先 (AOT) 编译器的一个优势在于它能够分析应用程序的运行时行为。例如,HotSpot 会跟踪 `if` 语句的每个分支执行了多少次。这些信息被称为“配置文件 (profile)”,它会被传递给二级 JIT 编译器(例如 Graal)。然后,二级 JIT 编译器假定 `if` 语句将继续以相同的方式运行,并利用配置文件中的信息来优化该语句。
AOT 编译器通常不具备分析信息,并且通常仅限于代码的静态视图。这意味着,除非使用启发式方法,否则 AOT 编译器会将每个 `if` 语句的每个分支视为在运行时发生的可能性相同;每个方法被调用的可能性与其他方法相同;每个循环重复的次数也相同。这使得 AOT 编译器处于劣势——如果没有分析信息,它很难生成与 JIT 编译器相同质量的机器代码。
配置文件引导优化 (PGO) 是一种将配置文件信息引入 AOT 编译器以提高其输出质量(在性能和大小方面)的技术。
注意:PGO 在 GraalVM 社区版中不可用。
什么是配置文件? #
配置文件是应用程序运行时期间某些事件发生次数的汇总日志。这些事件是根据对编译器做出更好决策有用的信息来选择的。示例包括:
- 此方法被调用了多少次?
- 此 `if` 语句执行 `true` 分支的次数?执行 `false` 分支的次数?
- 此方法分配了多少个对象?
- `String` 值被传递给特定 `instanceof` 检查的次数?
如何获取应用程序的配置文件? #
当在带有 JIT 编译器的 JVM 上运行应用程序时,应用程序的配置文件收集由运行时环境处理,开发人员无需额外的步骤。但是,创建配置文件会增加被分析应用程序的执行时间和内存使用开销。这会导致预热问题:应用程序只有在应用程序代码经过充分分析和 JIT 编译后才能达到可预测的峰值性能。对于长时间运行的应用程序,这种开销通常是值得的,因为它会在之后带来性能提升。另一方面,对于生命周期短的应用程序以及需要尽快以可预测性能启动的应用程序,这会适得其反。
为 AOT 编译的应用程序收集配置文件更为复杂,需要开发人员采取额外的步骤,但不会给最终应用程序带来开销。必须通过观察应用程序运行时来收集配置文件。这通常通过以特殊模式编译应用程序来实现,该模式会将插桩代码插入到应用程序二进制文件中。插桩代码会为配置文件感兴趣的事件增加计数器。包含插桩代码的二进制文件被称为插桩二进制文件,而添加这些计数器的过程被称为插桩。
自然,由于插桩代码的开销,应用程序的插桩二进制文件不如默认二进制文件那样高效,因此不建议在生产环境中运行它。但是,在插桩二进制文件上运行合成的代表性工作负载可以提供应用程序行为的代表性配置文件。在构建优化应用程序时,AOT 编译器同时拥有应用程序的静态视图和动态配置文件。因此,优化后的应用程序的性能优于默认的 AOT 编译应用程序。
配置文件如何“引导”优化? #
在编译期间,编译器必须就优化做出决策。例如,在以下方法中,函数内联优化需要决定哪些调用站点要内联,哪些不内联。
private int run(String[] args) {
if (args.length < 3) {
return handleNotEnoughArguments(args);
} else {
return doActualWork(args);
}
}
为了说明目的,假设内联优化对可以生成的代码量有限制,因此只能内联其中一个调用。仅从被编译代码的静态视图来看,`doActualWork()` 和 `handleNotEnoughArguments()` 的调用看起来几乎没有区别。在没有任何启发式方法的情况下,该阶段将不得不猜测哪个是更好的内联选择。然而,做出错误的选择可能会导致代码效率降低。假设 `run()` 在运行时最常被以正确数量的参数调用,那么内联 `handleNotEnoughArguments` 会增加编译单元的代码大小,但不会带来任何性能提升,因为大多数情况下仍然需要调用 `doActualWork()`。
拥有应用程序的运行时配置文件可以为编译器提供数据,以便区分这些调用。例如,如果运行时配置文件记录的 `if` 条件为 `false` 100 次,`true` 3 次,那么它应该内联 `doActualWork()`。这就是 PGO 的本质——利用配置文件中的信息为编译器在做出某些决策时提供数据基础。实际的决策和配置文件记录的实际事件因阶段而异,但前面的示例说明了大致思想。
请注意,PGO 要求在应用程序的插桩二进制文件上运行代表性工作负载。提供一个适得其反的配置文件(记录与应用程序实际运行时行为完全相反的配置文件)将是适得其反的。对于上述示例,这将是在插桩二进制文件上运行一个工作负载,该工作负载以过少的参数调用 `run()` 方法,而实际应用程序并非如此。这将导致内联阶段选择内联 `handleNotEnoughArguments`,从而降低优化二进制文件的性能。
因此,目标是尽可能收集与生产工作负载匹配的配置文件。为此的最佳做法是在插桩二进制文件上运行您期望在生产中运行的完全相同的工作负载。
有关更详细的用法概述,请参阅配置文件引导优化基本用法文档。