如何使用Profile-Guided Optimizations构建Clang和LLVM

简介

PGO (Profile-Guided Optimization,即配置引导优化) 允许你的编译器根据代码实际运行的方式更好地优化代码。用户报告称,将此应用于Clang和LLVM可以将整体编译时间减少20%。

本指南将引导你完成如何使用PGO构建Clang的过程,尽管它也适用于其他子项目,例如LLD。

如果你想使用PGO构建其他软件,请参阅PGO的最终用户文档

使用预配置的CMake缓存

请参阅 https://llvm.net.cn/docs/AdvancedBuilds.html#multi-stage-pgo

使用脚本

我们在 utils/collect_and_build_with_pgo.py 提供了一个脚本。此脚本已在一些Linux发行版上测试过,并且需要检出 LLVM、Clang 和 compiler-rt。 尽管名称如此,它会执行四次 Clang 的全新构建,因此可能需要一段时间才能运行完成。 有关如何运行它以及可用的不同选项的更多信息,请参阅脚本的 --help。 如果你想针对特定的用例(例如,编译特定的大型软件)最大限度地利用 PGO,请务必阅读下面关于“benchmark”选择的部分。

请注意,此脚本仅在少数Linux发行版上进行了测试。 我们非常感谢为其他平台添加支持的补丁。 :)

此脚本还支持 --dry-run 选项,该选项使其打印重要命令而不是运行它们。

选择 ‘benchmark’

当收集的profile代表用户计划如何使用编译器时,PGO效果最佳。 值得注意的是,如果你要以ARM为目标,那么llc构建x86_64代码的高度准确的profile并没有太大的帮助。

默认情况下,上面的脚本执行两件事以获得可靠的覆盖率。 它

  • 运行 Clang 和 LLVM 的所有 lit 测试,以及

  • 使用instrumented Clang构建Clang、LLVM以及所有其他可用的LLVM子项目。

总而言之,这些应该给你带来

  • 构建 C++ 的可靠覆盖率,

  • 构建 C 的良好覆盖率,

  • 运行优化的出色覆盖率,

  • 你的主机架构的后端出色覆盖率,以及

  • 其他架构的一些覆盖率(如果其他架构是受支持的后端)。

总之,这应该涵盖 Clang 和 LLVM 的各种用途。 如果你对编译器有非常具体的需求(例如,你的编译器旨在为四个不同的平台编译大型浏览器,或类似情况),你可能需要做其他事情。 这可以在脚本本身中配置。

使用PGO构建Clang

如果你不想使用脚本或 cmake 缓存,这部分简要介绍如何使用 PGO 构建 Clang/LLVM。

首先,你应该在本地检出至少 LLVM、Clang 和 compiler-rt。

接下来,从高层次来说,你将需要执行以下操作

  1. 构建标准的 Release Clang 和相关的 libclang_rt.profile 库

  2. 使用你上面构建的 Clang 构建 Clang,但要使用 instrumentation

  3. 使用 instrumented Clang 生成 profile,这包括两个步骤

  • 在代表用户将如何使用所述工具的任务上运行 instrumented Clang/LLVM/lld/etc。

  • 使用工具将上面生成的“raw” profile转换为单个最终的 PGO profile。

  1. 使用从你的 benchmark 收集的 profile 构建最终的 release Clang(以及你需要的任何其他二进制文件)

更详细的步骤

  1. 像往常一样配置 Clang 构建。 强烈建议你为此使用 Release 配置,因为它将用于构建另一个 Clang。 因为你需要 Clang 和支持库,所以你将需要构建 all 目标(例如,ninja allmake -j4 all)。

  2. 像上面一样配置 Clang 构建,但添加以下 CMake 参数

    • -DLLVM_BUILD_INSTRUMENTED=IR – 这使我们能够构建所有带有 instrumentation 的内容。

    • -DLLVM_BUILD_RUNTIME=No – 一些项目在通过 profiling 构建时会产生不良交互,并且不是必须构建的。 此标志会将其关闭。

    • -DCMAKE_C_COMPILER=/path/to/stage1/clang - 使用我们在步骤 1 中构建的 Clang。

    • -DCMAKE_CXX_COMPILER=/path/to/stage1/clang++ - 与上面相同。

在此构建目录中,你只需构建 clang 目标(以及你的 benchmark 所需的任何支持工具)。

  1. 如上所述,这有两个步骤:收集 profile 数据,然后将其整理成有用的形式

    1. 使用在步骤 2 中生成的 Clang 构建你的 benchmark。 推荐的“标准”benchmark是在你的 instrumented Clang 的构建目录中运行 check-clangcheck-llvm,并使用你的 instrumented Clang 对 Clang/LLVM 进行完整构建。 因此,创建另一个构建目录,并带有以下 CMake 参数

      • -DCMAKE_C_COMPILER=/path/to/stage2/clang - 使用我们在步骤 2 中构建的 Clang。

      • -DCMAKE_CXX_COMPILER=/path/to/stage2/clang++ - 与上面相同。

      如果你的用户是 debug info 的爱好者,你可能需要考虑使用 -DCMAKE_BUILD_TYPE=RelWithDebInfo 而不是 -DCMAKE_BUILD_TYPE=Release。 这将为 clang 的 debug info 部分提供更好的覆盖率,但将花费更长的时间才能完成,并将导致更大的构建目录。

      建议使用你的 instrumented Clang 构建 all 目标,因为更多的覆盖率通常更好。

  1. 你现在应该在 path/to/stage2/profiles/ 中有一些 *.profraw 文件。 你需要使用 llvm-profdata 合并这些文件(即使你只有一个! profile 合并也会将 profraw 转换为实际的 profile 数据)。 这可以使用 /path/to/stage1/llvm-profdata merge -output=/path/to/output/profdata.prof path/to/stage2/profiles/*.profraw 完成。

  1. 现在,构建你的最终的、PGO 优化的 Clang。 为此,你将需要将以下附加参数传递给 CMake。

    • -DLLVM_PROFDATA_FILE=/path/to/output/profdata.prof - 使用上一步中的 PGO profile。

    • -DCMAKE_C_COMPILER=/path/to/stage1/clang - 使用我们在步骤 1 中构建的 Clang。

    • -DCMAKE_CXX_COMPILER=/path/to/stage1/clang++ - 与上面相同。

    从这里,你可以构建你需要的任何目标。

    注意

    你可能会在构建输出中看到关于 profile 不匹配的警告。 这些通常是无害的。 要消除它们,你可以将 -DCMAKE_C_FLAGS='-Wno-backend-plugin' -DCMAKE_CXX_FLAGS='-Wno-backend-plugin' 添加到你的 CMake 调用中。

恭喜! 你现在拥有了一个使用 profile-guided optimizations 构建的 Clang,如果你愿意,可以删除除最终构建目录之外的所有目录。

如果这对你来说效果很好,并且你计划经常这样做,则可以进行一些小的优化:LLVM 和 Clang 有一个名为 tblgen 的工具,该工具在构建过程中构建和运行。 虽然在步骤 3 中构建它以获得覆盖率可能很好,但你的其他构建都不应从构建它中受益。 你可以将 CMake 选项 -DLLVM_NATIVE_TOOL_DIR=/path/to/stage1/bin 传递给步骤 2 及以后的步骤,以避免这些无用的重建。