基于配置文件优化

什么是基于配置文件优化? #

与提前编译 (AOT) 编译器相比,即时编译器 (JIT) 的一个优势在于它能够分析应用程序的运行时行为。例如,HotSpot 会跟踪每个 if 语句分支执行的次数。这种信息被称为“配置文件”,会被传递给第二层 JIT 编译器(例如 Graal)。第二层 JIT 编译器随后会假定 if 语句会继续以相同的方式运行,并使用配置文件中的信息来优化该语句。

AOT 编译器通常没有配置文件信息,并且通常仅限于代码的静态视图。这意味着,除了启发式算法之外,AOT 编译器会将每个 if 语句的每个分支视为在运行时同样可能出现;每个方法与任何其他方法一样可能被调用;每个循环重复的次数都相同。这使 AOT 编译器处于劣势,因为没有配置文件信息,很难生成与 JIT 编译器质量相同的机器代码。

基于配置文件优化 (PGO) 是一种将配置文件信息引入 AOT 编译器以提高其在性能和大小方面的输出质量的技术。

注意:GraalVM 社区版中不提供 PGO。

什么是配置文件#

配置文件是对应用程序运行时发生的某些事件的次数的总结日志。这些事件的选择基于哪些信息对编译器做出更佳决策有用。示例包括

  • 此方法被调用了多少次?
  • 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 预期在应用程序的检测二进制文件上运行代表性工作负载。提供反作用的配置文件(记录与应用程序的实际运行时行为完全相反的配置文件)将适得其反。对于上面的示例,这意味着在使用参数过少的检测二进制文件上运行工作负载,而实际应用程序没有这样做。这会导致内联阶段选择内联 handleNotEnoughArguments,从而降低优化二进制文件的性能。

因此,目标是在尽可能与生产工作负载匹配的工作负载上收集配置文件。这方面的黄金标准是在检测二进制文件上运行您希望在生产环境中运行的完全相同的工作负载。

有关更详细的用法概述,请访问 基于配置文件优化的基本用法 文档。

进一步阅读 #

联系我们