6.3.10 自定义 LLVM 后端的技巧
2 分钟阅读
6.3.10 自定义 LLVM 后端的技巧
高级
Kotlin/Native 编译器使用 LLVM 为不同目标平台优化并生成二进制可执行文件。编译时间中相当可观的一部分也花在 LLVM 上,对于大型应用,这可能最终耗时长得无法接受。
你可以自定义 Kotlin/Native 使用 LLVM 的方式,并调整优化 pass 的列表。
查看构建日志
我们来看一下构建日志,以了解有多少编译时间花在 LLVM 优化 pass 上:
- 使用
-Pkotlin.internal.compiler.arguments.log.level=warning选项运行linkRelease*Gradle 任务,让 Gradle 输出 LLVM 性能分析细节,例如:
| |
执行期间,该任务会打印必要的编译器参数,例如:
> Task :linkReleaseExecutableMacosArm64
Run in-process tool "konanc"
Entry point method = org.jetbrains.kotlin.cli.utilities.MainKt.daemonMain
Classpath = [
/Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/konan/lib/kotlin-native-compiler-embeddable.jar
/Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/konan/lib/trove4j.jar
]
Arguments = [
-Xinclude=...
-library
/Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/klib/common/stdlib
-no-endorsed-libs
-nostdlib
...
]
- 用这些参数再加上
-Xprofile-phases参数运行命令行编译器,例如:
| |
- 查看构建日志中生成的输出。日志可能包含数万行;带 LLVM 性能分析的部分在末尾。
下面是一个简单 Kotlin/Native 程序这样运行的摘录:
Frontend: 275 msec
PsiToIr: 1186 msec
...
... 30k lines
...
LinkBitcodeDependencies: 476 msec
StackProtectorPhase: 0 msec
MandatoryBitcodeLLVMPostprocessingPhase: 2 msec
===-------------------------------------------------------------------------===
Pass execution timing report
===-------------------------------------------------------------------------===
Total Execution Time: 6.7726 seconds (6.7192 wall clock)
---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name ---
0.9778 ( 22.4%) 0.5043 ( 21.0%) 1.4821 ( 21.9%) 1.4628 ( 21.8%) InstCombinePass
0.3827 ( 8.8%) 0.2497 ( 10.4%) 0.6323 ( 9.3%) 0.6283 ( 9.4%) InlinerPass
0.2815 ( 6.4%) 0.1792 ( 7.5%) 0.4608 ( 6.8%) 0.4555 ( 6.8%) SimplifyCFGPass
...
0.6444 (100.0%) 0.5474 (100.0%) 1.1917 (100.0%) 1.1870 (100.0%) Total
ModuleBitcodeOptimization: 8118 msec
...
LTOBitcodeOptimization: 1399 msec
...
Kotlin/Native 编译器会运行两个独立的 LLVM 优化序列:模块 pass 和链接时 pass。对于典型的编译,这两条流水线是接连运行的,唯一真正的区别在于它们运行哪些 LLVM 优化 pass。
在上面的日志中,两个 LLVM 优化是 ModuleBitcodeOptimization 和 LTOBitcodeOptimization。那些格式化的表格就是优化的输出,带有每个 pass 的耗时。
自定义 LLVM 优化 pass
如果上面某个 pass 看起来耗时长得不合理,你可以跳过它。不过这可能损害运行时性能,因此之后应当检查基准测试的性能变化。
目前还没有直接的方式禁用某个给定的 pass。不过你可以通过以下编译器选项提供要运行的新 pass 列表:
| 选项 | 发布版二进制文件的默认值 |
| -Xllvm-module-passes | "default<O3>" |
| -Xllvm-lto-passes | "internalize,globaldce,lto<O3>" |
这些默认值会展开成一长串实际 pass,你需要从中排除不想要的那些。
要获得实际 pass 的列表,请运行 opt 工具,它会随 LLVM 发行版自动下载到 ~/.konan/dependencies/llvm-{VERSION}-{ARCH}-{OS}-dev-{BUILD}/bin 目录中。
例如,要获得链接时 pass 的列表,请运行:
| |
这会输出一条警告和一长串 pass,具体取决于 LLVM 版本。
opt 工具给出的 pass 列表与 Kotlin/Native 编译器实际运行的 pass 之间有两处差异:
- 由于
opt是调试工具,它会包含一个或多个verifypass,而这些通常不会运行。 - Kotlin/Native 禁用了
devirtpass,因为 Kotlin 编译器自己已经做了这些。
禁用任何 pass 之后,一定要重新运行性能测试,检查运行时性能的下降是否可以接受。