.NET8动态PGO简析

💡 原文中文,约4900字,阅读约需12分钟。
📝

内容提要

动态PGO是.NET8中性能提升的关键,通过分层编译和自动优化代码,提高了代码质量和程序运行速度。R2R也被纳入优化,进一步提升了动态PGO的性能。

🔎

延伸解读

分层编译的阈值机制

文章指出,只有少数方法能进入Tier1,这依赖于JIT收集的运行数据。例如,方法运行时间超过60ms且调用次数超过2次(.NET8 R2R函数的阈值)才会触发优化。这种阈值设计平衡了编译开销与运行性能,避免为不常执行的方法浪费优化时间。

动态PGO的自动化优势

与需要额外配置的静态PGO不同,动态PGO完全由JIT在运行时自动收集信息并优化,无需开发者干预。这降低了使用门槛,使得性能提升对大多数应用透明,但优化效果依赖于程序的实际运行路径。

R2R与分层编译的整合

R2R预编译代码被纳入分层编译后,会先以优化但未检查的状态运行,随后根据运行情况重新编译。这种整合让R2R代码也能受益于动态PGO,避免了因预编译而错过运行时优化机会,从而提升整体性能。

Tier0的常量折叠优化

尽管Tier0以快速编译为目标,但仍会进行常量折叠等优化。例如,对于泛型方法MaybePrint,.NET8的Tier0能识别出分支永远不会执行,从而生成更精简的代码。这减少了不必要的指令,提升了编译效率和代码质量。

❓

Q&A

.NET8 的性能提升主要归功于什么?

.NET8 的性能提升主要归功于动态 PGO(Profile-Guided Optimization)。

什么是分层编译?它如何工作?

分层编译是 .NET Core 3.0 引入的 JIT 功能,允许方法被编译多次。第一次编译为 0 层(tier0),优先考虑编译速度,进行最小优化;当方法运行次数或时间超过阈值时,会进入 1 层(tier1),基于 0 层收集的信息进行优化,生成更高质量的代码。

动态 PGO 和静态 PGO 有什么区别?

静态 PGO 需要额外的编译器或配置,在代码关键位置收集信息,下次运行根据信息重新构建应用程序;而动态 PGO 通过分层编译,由 JIT 自行收集、判断和优化,无需任何额外的开发工作或基础设施配置。

OSR 是什么?它解决了什么问题?

OSR(On-Stack Replacement)是 .NET 中引入的技术,用于解决方法内存在长时间运行的循环但方法本身调用次数少的问题。当循环计数达到一定阈值时,JIT 会编译该方法的新优化版本,并从最小优化版本切换到新优化版本继续执行,从而提升性能。

R2R 在动态 PGO 中扮演什么角色?

R2R 是预编译镜像,包含完全的二进制运行代码。将 R2R 纳入分层编译后,R2R 方法在运行一定次数后会被 JIT 重新优化编译,从而进一步提升动态 PGO 的性能。如果不纳入,会阻碍动态 PGO 的性能提升。

.NET8 中 0 层编译会进行优化吗?

是的,0 层编译虽然优先考虑编译速度,但仍会进行一定的代码优化,例如常量折叠,以减少生成代码的大小,提升整体性能。

🏷️

标签

➡️

继续阅读