使用配置文件引导的优化 (PGO)

Android 编译系统支持在具有蓝图编译规则的原生 Android 模块上使用 Clang 的配置文件引导优化 (PGO)。本页面介绍了 Clang PGO、如何持续生成和更新用于 PGO 的配置文件,以及如何将 PGO 集成到编译系统(包含用例)。

Clang PGO 简介

Clang 可以使用两类配置文件来执行配置文件引导的优化:

  • 基于插桩的配置文件是从插桩的目标程序生成的。这些配置文件很详细,且会产生很高的运行时开销。
  • 基于采样的配置文件通常通过对硬件计数器进行采样生成。此类配置文件产生的运行时开销较低,并且无需对二进制文件进行任何插桩或修改即可收集。详细程度不如基于插桩的配置文件。

所有配置文件都应该从执行应用的典型行为的代表性工作负载生成。虽然 Clang 同时支持基于 AST (-fprofile-instr-generate) 和基于 LLVM IR (-fprofile-generate)) 配置文件,但 Android 仅针对基于插桩的 PGO 支持基于 LLVM IR 的配置文件。

需要为配置文件集合编译以下标记:

  • -fprofile-generate,适用于基于 IR 的插桩。借助此选项,后端可使用加权最小生成树方法来减少插桩点的数量,并优化它们在低权重边缘的放置(对于链接步骤也使用此选项)。Clang 驱动程序会自动将分析运行时 (libclang_rt.profile-arch-android.a) 传递给链接器。该库包含可在程序退出时将配置文件写入磁盘的例程。
  • -gline-tables-only,适用于基于采样的配置文件收集,可生成最少的调试信息。

配置文件可用于 PGO(分别针对基于插桩的配置文件和基于采样的配置文件使用 -fprofile-instr-use=pathname-fprofile-sample-use=pathname)。

注意:当对代码进行更改时,如果 Clang 无法再使用配置文件数据,则会生成一条 -Wprofile-instr-out-of-date 警告。

使用 PGO

要使用 PGO,请按以下步骤操作:

  1. 通过将 -fprofile-generate 传递给编译器和链接器,使用插桩编译库/可执行文件。
  2. 通过在插桩二进制文件上运行代表性工作负载来收集配置文件。
  3. 使用 llvm-profdata 实用程序对配置文件进行后处理(有关详情,请参阅处理 LLVM 配置文件)。
  4. 通过将 -fprofile-use=<>.profdata 传递给编译器和链接器,使用配置文件应用 PGO。

对于 Android 中的 PGO,应离线收集配置文件并随代码签入,以确保编译可重现。无论代码如何变化,配置文件都可一直使用,但必须定期(或在 Clang 发出配置文件过时的警告时)重新生成。

收集配置文件

Clang 可以使用通过以下方式收集的配置文件:使用库的插桩编译运行基准,或在运行基准时对硬件计数器进行采样。目前,Android 不支持使用基于采样的配置文件收集,因此您必须使用插桩编译收集配置文件:

  1. 确定一个基准以及由该基准统一执行的一组库。
  2. pgo 属性添加到该基准和各个库(请参阅下文了解详情)。
  3. 使用以下命令生成包含这些库的插桩副本的 Android 编译:
    make ANDROID_PGO_INSTRUMENT=benchmark

benchmark 是占位符,用于标识在编译时插桩的库集合。实际的代表性输入(也可能是链接到进行基准化的库的其他可执行文件)并非专用于 PGO,不在本文档的讨论范围内。

  1. 在设备上刷写或同步插桩编译。
  2. 运行基准以收集配置文件。
  3. 使用下文中介绍的 llvm-profdata 工具对配置文件进行后处理,并使其做好签入源代码树的准备。

在编译时使用配置文件

将配置文件签入 Android 树中的 toolchain/pgo-profiles。名称应与库的 pgo 属性的 profile_file 子属性中指定的名称一致。编译库时,编译系统会自动将配置文件传递到 Clang。您可以将 ANDROID_PGO_DISABLE_PROFILE_USE 环境变量设置为 true,以暂时停用 PGO 并衡量其性能优势。

要指定额外的产品专用配置文件目录,请将其附加到 BoardConfig.mk 中的 PGO_ADDITIONAL_PROFILE_DIRECTORIES make 变量。如果指定了其他路径,则这些路径中的配置文件将替换 toolchain/pgo-profiles 中的配置文件。

当使用 makedist 目标生成版本映像时,编译系统会将缺失的配置文件的名称写入 $DIST_DIR/pgo_profile_file_missing.txt。您可以检查此文件,看看哪些配置文件遭到了意外删除(以静默方式停用 PGO)。

在 Android.bp 文件中启用 PGO

要在 Android.bp 文件中为原生模块启用 PGO,只需指定 pgo 属性即可。此属性具有以下子属性:

属性 说明
instrumentation 对于使用插桩方法的 PGO,设置为 true。默认值为 false
sampling 当前不受支持。 对于使用采样方法的 PGO,设置为 true。默认值为 false
benchmarks 字符串列表。如果该列表中的任何基准是在 ANDROID_PGO_INSTRUMENT 编译选项中指定的,则说明此模块是专为分析而构建的。
profile_file 要与 PGO 结合使用的配置文件(相对于 toolchain/pgo-profile)。编译系统通过将此文件添加到 $DIST_DIR/pgo_profile_file_missing.txt 来警告此文件不存在,除非将 enable_profile_use 属性设置为 false 或者ANDROID_PGO_NO_PROFILE_USE 编译变量设置为 true
enable_profile_use 如果在编译期间不应使用配置文件,则设置为 false。可在引导期间用来启用配置文件收集或暂时停用 PGO。默认值为 true
cflags 在插桩编译期间使用的其他标记的列表。

包含 PGO 的模块示例:

cc_library {
    name: "libexample",
    srcs: [
        "src1.cpp",
        "src2.cpp",
    ],
    static: [
        "libstatic1",
        "libstatic2",
    ],
    shared: [
        "libshared1",
    ]
    pgo: {
        instrumentation: true,
        benchmarks: [
            "benchmark1",
            "benchmark2",
        ],
        profile_file: "example.profdata",
    }
}

如果基准 benchmark1benchmark2libstatic1libstatic2libshared1 库执行代表性行为,则这些库的 pgo 属性也可以包括这些基准。Android.bp 中的 defaults 模块可以包含一组库的常见 pgo 规范,以避免针对多个模块重复相同的编译规则。

要为某个架构选择不同的配置文件或有选择性地停用 PGO,请按架构指定 profile_fileenable_profile_usecflags 属性。具体示例(架构目标以粗体显示)如下所示:

cc_library {
    name: "libexample",
    srcs: [
          "src1.cpp",
          "src2.cpp",
    ],
    static: [
          "libstatic1",
          "libstatic2",
    ],
    shared: [
          "libshared1",
    ],
    pgo: {
         instrumentation: true,
         benchmarks: [
              "benchmark1",
              "benchmark2",
         ],
    }

    target: {
         android_arm: {
              pgo: {
                   profile_file: "example_arm.profdata",
              }
         },
         android_arm64: {
              pgo: {
                   profile_file: "example_arm64.profdata",
              }
         }
    }
}

要在执行基于插桩的分析期间解析对分析运行时库的引用,请将编译标记 -fprofile-generate 传递至链接器。使用 PGO 插桩的静态库、所有共享库以及任何直接依赖于静态库的二进制文件也必需针对 PGO 进行插桩。不过,此类共享库或可执行文件不需要使用 PGO 配置文件,而且它们的 enable_profile_use 属性可以设置为 false。除此限制外,您可以将 PGO 应用于任何静态库、共享库或可执行文件。

处理 LLVM 配置文件

执行插桩库或可执行文件会在 /data/local/tmp 中生成一个名为 default_unique_id_0.profraw 的配置文件(其中 unique_id 是一个数字哈希值,对此库来说是唯一的)。如果此文件已存在,则分析运行时会在写入配置文件时将新老配置文件合并。要更改配置文件的位置,请在运行时设置 LLVM_PROFILE_FILE 环境变量。

然后使用 llvm-profdata 实用程序将 .profraw 文件转换(可能会合并多个 .profraw 文件)为 .profdata 文件:

  llvm-profdata merge -output=profile.profdata <.profraw and/or .profdata files>

然后,可以将 profile.profdata 签入源代码树,以供在编译时使用。

如果某个基准运行期间加载了多个插桩二进制文件/库,则每个库都会生成一个单独的.profraw 文件(包含单独的唯一 ID)。通常,所有这些文件都可以合并为一个 .profdata 文件,并用于 PGO 编译。如果某个库由另一个基准执行,则必须使用来自两个基准的配置文件优化该库。在这种情况下,llvm-profdatashow 选项非常有用:

  llvm-profdata merge -output=default_unique_id.profdata default_unique_id_0.profraw
llvm-profdata show -all-functions default_unique_id.profdata

要将 unique_id 映射到各个库,请针对相应库独有的函数名称搜索各个 unique_id show 输出。

案例研究:适用于 ART 的 PGO

该案例研究将 ART 作为一个相关的示例;但是,它并不能准确描述为 ART 或其相互依赖关系分析的一系列实际库。

ART 中的 dex2oat 预编译器依赖 libart-compiler.so,后者则依赖 libart.so。ART 运行时主要在 libart.so 中实现。编译器和运行时的基准有所不同:

基准 分析的库
dex2oat dex2oat(可执行文件)、libart-compiler.solibart.so
art_runtime libart.so
  1. 将以下 pgo 属性添加到 dex2oatlibart-compiler.so
        pgo: {
            instrumentation: true,
            benchmarks: ["dex2oat",],
            profile_file: "dex2oat.profdata",
        }
  2. 将以下 pgo 属性添加到 libart.so
        pgo: {
            instrumentation: true,
            benchmarks: ["art_runtime", "dex2oat",],
            profile_file: "libart.profdata",
        }
  3. 使用以下命令为 dex2oatart_runtime 基准创建插桩编译:
        make ANDROID_PGO_INSTRUMENT=dex2oat
        make ANDROID_PGO_INSTRUMENT=art_runtime
  4. 或者,使用以下命令创建一个包含所有插桩库的插桩编译:

        make ANDROID_PGO_INSTRUMENT=dex2oat,art_runtime
        (or)
        make ANDROID_PGO_INSTRUMENT=ALL

    第二个命令会编译所有启用 PGO 的模块,以进行分析。

  5. 运行执行 dex2oatart_runtime 的基准以获得:
    • 三个来自 dex2oat.profraw 文件(dex2oat_exe.profdatadex2oat_libart-compiler.profdatadexeoat_libart.profdata),这三个文件均使用处理 LLVM 配置文件中说明的方法标识。
    • 一个 art_runtime_libart.profdata
  6. 使用以下命令为 dex2oat 可执行文件和 libart-compiler.so 生成一个通用的 profdata 文件:
    llvm-profdata merge -output=dex2oat.profdata \
        dex2oat_exe.profdata dex2oat_libart-compiler.profdata
  7. 通过合并来自两个基准的配置文件,获取 libart.so 的配置文件:
    llvm-profdata merge -output=libart.profdata \
        dex2oat_libart.profdata art_runtime_libart.profdata

    来自两个配置文件的 libart.so 的原始计数可能是不同的,因为不同基准的测试用例数量以及运行时长存在差异。在这种情况下,您可以使用以下加权合并命令:

    llvm-profdata merge -output=libart.profdata \
        -weighted-input=2,dex2oat_libart.profdata \
        -weighted-input=1,art_runtime_libart.profdata

    上述命令将两倍的权重分配给来自 dex2oat 的配置文件。实际权重应取决于领域知识或实验。

  8. 将配置文件 dex2oat.profdatalibart.profdata 签入 toolchain/pgo-profiles,以供在编译时使用。