自定义 LLVM 后端的技巧
Kotlin/Native 编译器使用 LLVM 来针对不同的目标平台进行优化, 并生成二进制可执行文件. 编译时间中有相当一部分也耗费在 LLVM 上, 对于大型应用程序, 这可能会导致耗时长到无法接受.
你可以自定义 Kotlin/Native 使用 LLVM 的方式, 并调整优化 pass 的列表.
检查构建日志
让我们查看构建日志, 了解编译时间中有多少花费在 LLVM 优化 pass 上:
使用
-Pkotlin.internal.compiler.arguments.log.level=warning选项运行linkRelease*Gradle task, 让 Gradle 输出 LLVM 性能分析的详细信息, 例如:./gradlew linkReleaseExecutableMacosArm64 -Pkotlin.internal.compiler.arguments.log.level=warning在执行过程中, task 会打印输出必要的编译器参数, 例如:
> 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参数, 运行命令行编译器, 例如:/Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/bin/kotlinc-native \ -Xinclude=... \ -library /Users/user/.konan/kotlin-native-prebuilt-macos-aarch64-2.2.0/klib/common/stdlib \ ... \ -Xprofile-phases查看构建日志中生成的输出. 日志可能包含数万行; 包含 LLVM 性能分析的部分在最末尾.
以下是对一个简单 Kotlin/Native 程序运行时的输出节选:
Kotlin/Native 编译器运行两个独立的 LLVM 优化序列: 模块 pass 和链接时(link-time) pass. 对于一次典型的编译, 这两个管道(pipeline)依次运行, 唯一真正的区别在于它们运行的 LLVM 优化 pass 不同.
在上面的日志中, 两个 LLVM 优化是 ModuleBitcodeOptimization 和 LTOBitcodeOptimization. 格式化的表格是优化的输出, 包含每个 pass 耗费的时间.
自定义 LLVM 优化 pass
如果上述某个 pass 耗费的时间看起来过长, 你可以跳过它. 但是, 这可能会损害运行时性能, 因此你应该在之后检查性能基准测试的变化.
目前, 没有直接的方法来 禁用特定的 pass. 但是, 你可以使用以下编译器选项, 来提供一个新的 pass 列表来运行:
选项 | Release 二进制文件的默认值 |
|---|---|
|
|
|
|
默认值会展开为一个很长的实际 pass 列表, 你需要从中排除不需要的 pass.
要获取实际的 pass 列表, 请运行 opt 工具, 它会随 LLVM 发行版自动下载到 ~/.konan/dependencies/llvm-{VERSION}-{ARCH}-{OS}-dev-{BUILD}/bin 目录.
例如, 要获取链接时 pass 的列表, 请运行:
这个命令会输出一个警告, 和一个很长的 pass 列表, 具体内容取决于 LLVM 版本.
opt 工具的 pass 列表与 Kotlin/Native 编译器实际运行的 pass 之间有两个区别:
由于
opt是调试工具, 它包含一个或多个verifypass, 而这些 pass 通常不会运行.Kotlin/Native 禁用了
devirtpass, 因为 Kotlin 编译器自己已经执行了这些操作.
禁用任何 pass 之后, 一定要重新运行性能测试, 检查运行时性能的降低是否在可接受的范围内.