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

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

注意:本文档介绍了如何在 Android 平台中使用 PGO。如需了解如何在 Android 应用中使用 PGO,请访问此页面

Clang PGO 简介

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

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

所有配置文件都应该从符合应用典型行为方式的代表性工作负载生成。虽然 Clang 同时支持基于 AST (-fprofile-instr-generate) 和基于 LLVM IR (-fprofile-generate)) 的配置文件,但如果是基于插桩的 PGO,Android 仅支持基于 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,应离线收集配置文件并随代码签入,以确保 build 可重现。无论代码如何变化,配置文件都可一直使用,但必须定期(或在 Clang 发出配置文件过时的警告时)重新生成。

收集配置文件

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

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

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

  1. 在设备上刷写或同步插桩 build。
  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)。除非将 enable_profile_use 属性设置为 false或者ANDROID_PGO_NO_PROFILE_USE 构建变体设置为 true,否则构建将通过将此文件添加到 $DIST_DIR/pgo_profile_file_missing.txt 来警告此文件不存在。
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 属性。示例(架构 target 以粗体显示)如下所示:

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 是一个数字哈希值,唯一对应于此库)。如果此文件已存在,性能分析运行时会在写入配置文件时将新老配置文件合并。请注意,应用开发者无法访问 /data/local/tmp;而是应该改用类似 /storage/emulated/0/Android/data/packagename/files 的路径。如需更改配置文件的位置,请在运行时设置 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 基准创建插桩 build:
        make ANDROID_PGO_INSTRUMENT=dex2oat
        make ANDROID_PGO_INSTRUMENT=art_runtime
  4. 或者,使用以下命令创建一个包含所有插桩库的插桩 build:

        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,以在构建期间使用。