如何使用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(例如,编译特定的大型软件),请阅读下面关于“基准测试”选择的章节。

请注意,此脚本仅在少数Linux发行版上进行了测试。与往常一样,非常欢迎添加其他平台支持的补丁。 :)

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

选择“基准测试”

当收集的配置文件代表用户计划如何使用编译器时,PGO的效果最佳。值得注意的是,如果您的目标是ARM,则x86_64代码构建的llc的高精度配置文件并没有太大帮助。

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

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

  • 使用带插装的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,但要进行插装

  3. 使用插装的Clang生成配置文件,这包括两个步骤

  • 在代表用户如何使用这些工具的任务上运行插装的Clang/LLVM/lld等。

  • 使用工具将上面生成的“原始”配置文件转换为单个最终PGO配置文件。

  1. 使用从基准测试中收集的配置文件构建最终的Release版Clang(以及您需要的任何其他二进制文件)

更详细的步骤

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

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

    • -DLLVM_BUILD_INSTRUMENTED=IR – 这会导致我们构建所有带插装的内容。

    • -DLLVM_BUILD_RUNTIME=No – 一些项目在使用配置文件构建时存在不良交互,并且不需要构建。此标志将其关闭。

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

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

在此构建目录中,您只需要构建clang目标(以及基准测试所需的任何支持工具)。

  1. 如上所述,这有两个步骤:收集配置文件数据,然后将其转换为有用的形式

    1. 使用步骤2中生成的Clang构建您的基准测试。“标准”建议的基准测试是在插装的Clang的构建目录中运行check-clangcheck-llvm,并使用插装的Clang完整构建Clang/LLVM。因此,创建另一个构建目录,并使用以下CMake参数:

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

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

      如果您的用户喜欢调试信息,您可能需要考虑使用-DCMAKE_BUILD_TYPE=RelWithDebInfo而不是-DCMAKE_BUILD_TYPE=Release。这将提供对clang的调试信息部分的更好覆盖率,但完成时间更长,并且会产生更大的构建目录。

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

  1. 您现在应该在path/to/stage2/profiles/中有一些*.profraw文件。您需要使用llvm-profdata合并这些文件(即使您只有一个!配置文件合并会将profraw转换为实际的配置文件数据)。这可以通过/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配置文件。

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

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

    从这里,您可以构建所需的任何目标。

    注意

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

恭喜!您现在拥有了一个使用配置文件引导优化构建的Clang,如果您愿意,可以删除除最终构建目录之外的所有目录。

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