[InstrProf] Add options to profile function groups

Add two options, `-fprofile-function-groups=N` and `-fprofile-selected-function-group=i` used to partition functions into `N` groups and only instrument the functions in group `i`. Similar options were added to xray in https://reviews.llvm.org/D87953 and the goal is the same; to reduce instrumented size overhead by spreading the overhead across multiple builds. Raw profiles from different groups can be added like normal using the `llvm-profdata merge` command.

Reviewed By: ianlevesque

Differential Revision: https://reviews.llvm.org/D129594
This commit is contained in:
Ellis Hoag 2022-07-14 11:40:53 -07:00
parent 140bfdca60
commit af58684f27
10 changed files with 143 additions and 6 deletions

View File

@ -2329,6 +2329,10 @@ Use instrumentation data for profile-guided optimization
Filename defining the list of functions/files to instrument
.. option:: -fprofile-function-groups=<N>, -fprofile-selected-function-group=<i>
Partition functions into <N> groups and select only functions in group <i> to be instrumented
.. option:: -fprofile-remapping-file=<file>
Use the remappings described in <file> to match the profile data against names in the program

View File

@ -2513,6 +2513,32 @@ When the file contains only excludes, all files and functions except for the
excluded ones will be instrumented. Otherwise, only the files and functions
specified will be instrumented.
Instrument function groups
^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes it is desirable to minimize the size overhead of instrumented
binaries. One way to do this is to partition functions into groups and only
instrument functions in a specified group. This can be done using the
`-fprofile-function-groups` and `-fprofile-selected-function-group` options.
.. option:: -fprofile-function-groups=<N>, -fprofile-selected-function-group=<i>
The following uses 3 groups
.. code-block:: console
$ clang++ -Oz -fprofile-generate=group_0/ -fprofile-function-groups=3 -fprofile-selected-function-group=0 code.cc -o code.0
$ clang++ -Oz -fprofile-generate=group_1/ -fprofile-function-groups=3 -fprofile-selected-function-group=1 code.cc -o code.1
$ clang++ -Oz -fprofile-generate=group_2/ -fprofile-function-groups=3 -fprofile-selected-function-group=2 code.cc -o code.2
After collecting raw profiles from the three binaries, they can be merged into
a single profile like normal.
.. code-block:: console
$ llvm-profdata merge -output=code.profdata group_*/*.profraw
Profile remapping
^^^^^^^^^^^^^^^^^

View File

@ -213,6 +213,10 @@ CODEGENOPT(AtomicProfileUpdate , 1, 0) ///< Set -fprofile-update=atomic
ENUM_CODEGENOPT(ProfileInstr, ProfileInstrKind, 2, ProfileNone)
/// Choose profile kind for PGO use compilation.
ENUM_CODEGENOPT(ProfileUse, ProfileInstrKind, 2, ProfileNone)
/// Partition functions into N groups and select only functions in group i to be
/// instrumented. Selected group numbers can be 0 to N-1 inclusive.
VALUE_CODEGENOPT(ProfileTotalFunctionGroups, 32, 1)
VALUE_CODEGENOPT(ProfileSelectedFunctionGroup, 32, 0)
CODEGENOPT(CoverageMapping , 1, 0) ///< Generate coverage mapping regions to
///< enable code coverage analysis.
CODEGENOPT(DumpCoverageMapping , 1, 0) ///< Dump the generated coverage mapping

View File

@ -1333,6 +1333,15 @@ def fprofile_list_EQ : Joined<["-"], "fprofile-list=">,
Group<f_Group>, Flags<[CC1Option, CoreOption]>,
HelpText<"Filename defining the list of functions/files to instrument">,
MarshallingInfoStringVector<LangOpts<"ProfileListFiles">>;
def fprofile_function_groups : Joined<["-"], "fprofile-function-groups=">,
Group<f_Group>, Flags<[CC1Option]>, MetaVarName<"<N>">,
HelpText<"Partition functions into N groups and select only functions in group i to be instrumented using -fprofile-selected-function-group">,
MarshallingInfoInt<CodeGenOpts<"ProfileTotalFunctionGroups">, "1">;
def fprofile_selected_function_group :
Joined<["-"], "fprofile-selected-function-group=">, Group<f_Group>,
Flags<[CC1Option]>, MetaVarName<"<i>">,
HelpText<"Partition functions into N groups using -fprofile-function-groups and select only functions in group i to be instrumented. The valid range is 0 to N-1 inclusive">,
MarshallingInfoInt<CodeGenOpts<"ProfileSelectedFunctionGroup">>;
def fswift_async_fp_EQ : Joined<["-"], "fswift-async-fp=">,
Group<f_Group>, Flags<[CC1Option, CC1AsOption, CoreOption]>, MetaVarName<"<option>">,
HelpText<"Control emission of Swift async extended frame info">,

View File

@ -852,7 +852,7 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
}
if (CGM.getCodeGenOpts().getProfileInstr() != CodeGenOptions::ProfileNone)
if (CGM.isProfileInstrExcluded(Fn, Loc))
if (CGM.isFunctionBlockedFromProfileInstr(Fn, Loc))
Fn->addFnAttr(llvm::Attribute::NoProfile);
unsigned Count, Offset;

View File

@ -58,6 +58,7 @@
#include "llvm/IR/Module.h"
#include "llvm/IR/ProfileSummary.h"
#include "llvm/ProfileData/InstrProfReader.h"
#include "llvm/Support/CRC.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
@ -2843,8 +2844,8 @@ bool CodeGenModule::imbueXRayAttrs(llvm::Function *Fn, SourceLocation Loc,
return true;
}
bool CodeGenModule::isProfileInstrExcluded(llvm::Function *Fn,
SourceLocation Loc) const {
bool CodeGenModule::isFunctionBlockedByProfileList(llvm::Function *Fn,
SourceLocation Loc) const {
const auto &ProfileList = getContext().getProfileList();
// If the profile list is empty, then instrument everything.
if (ProfileList.isEmpty())
@ -2871,6 +2872,20 @@ bool CodeGenModule::isProfileInstrExcluded(llvm::Function *Fn,
return ProfileList.getDefault();
}
bool CodeGenModule::isFunctionBlockedFromProfileInstr(
llvm::Function *Fn, SourceLocation Loc) const {
if (isFunctionBlockedByProfileList(Fn, Loc))
return true;
auto NumGroups = getCodeGenOpts().ProfileTotalFunctionGroups;
if (NumGroups > 1) {
auto Group = llvm::crc32(arrayRefFromStringRef(Fn->getName())) % NumGroups;
if (Group != getCodeGenOpts().ProfileSelectedFunctionGroup)
return true;
}
return false;
}
bool CodeGenModule::MustBeEmitted(const ValueDecl *Global) {
// Never defer when EmitAllDecls is specified.
if (LangOpts.EmitAllDecls)

View File

@ -1340,9 +1340,15 @@ public:
bool imbueXRayAttrs(llvm::Function *Fn, SourceLocation Loc,
StringRef Category = StringRef()) const;
/// Returns true if function at the given location should be excluded from
/// profile instrumentation.
bool isProfileInstrExcluded(llvm::Function *Fn, SourceLocation Loc) const;
/// \returns true if \p Fn at \p Loc should be excluded from profile
/// instrumentation by the SCL passed by \p -fprofile-list.
bool isFunctionBlockedByProfileList(llvm::Function *Fn,
SourceLocation Loc) const;
/// \returns true if \p Fn at \p Loc should be excluded from profile
/// instrumentation.
bool isFunctionBlockedFromProfileInstr(llvm::Function *Fn,
SourceLocation Loc) const;
SanitizerMetadata *getSanitizerMetadata() {
return SanitizerMD.get();

View File

@ -956,6 +956,27 @@ static void addPGOAndCoverageFlags(const ToolChain &TC, Compilation &C,
CmdArgs.push_back("-fprofile-update=atomic");
}
int FunctionGroups = 1;
int SelectedFunctionGroup = 0;
if (const auto *A = Args.getLastArg(options::OPT_fprofile_function_groups)) {
StringRef Val = A->getValue();
if (Val.getAsInteger(0, FunctionGroups) || FunctionGroups < 1)
D.Diag(diag::err_drv_invalid_int_value) << A->getAsString(Args) << Val;
}
if (const auto *A =
Args.getLastArg(options::OPT_fprofile_selected_function_group)) {
StringRef Val = A->getValue();
if (Val.getAsInteger(0, SelectedFunctionGroup) ||
SelectedFunctionGroup < 0 || SelectedFunctionGroup >= FunctionGroups)
D.Diag(diag::err_drv_invalid_int_value) << A->getAsString(Args) << Val;
}
if (FunctionGroups != 1)
CmdArgs.push_back(Args.MakeArgString("-fprofile-function-groups=" +
Twine(FunctionGroups)));
if (SelectedFunctionGroup != 0)
CmdArgs.push_back(Args.MakeArgString("-fprofile-selected-function-group=" +
Twine(SelectedFunctionGroup)));
// Leave -fprofile-dir= an unused argument unless .gcda emission is
// enabled. To be polite, with '-fprofile-arcs -fno-profile-arcs' consider
// the flag used. There is no -fno-profile-dir, so the user has no

View File

@ -0,0 +1,24 @@
// RUN: %clang -fprofile-generate -fprofile-function-groups=3 -fprofile-selected-function-group=0 -emit-llvm -S %s -o - | FileCheck %s --check-prefixes=CHECK,SELECT0
// RUN: %clang -fprofile-generate -fprofile-function-groups=3 -fprofile-selected-function-group=1 -emit-llvm -S %s -o - | FileCheck %s --check-prefixes=CHECK,SELECT1
// RUN: %clang -fprofile-generate -fprofile-function-groups=3 -fprofile-selected-function-group=2 -emit-llvm -S %s -o - | FileCheck %s --check-prefixes=CHECK,SELECT2
// Group 0
// SELECT0-NOT: noprofile
// SELECT1: noprofile
// SELECT2: noprofile
// CHECK: define {{.*}} @hoo()
void hoo() {}
// Group 1
// SELECT0: noprofile
// SELECT1-NOT: noprofile
// SELECT2: noprofile
// CHECK: define {{.*}} @goo()
void goo() {}
// Group 2
// SELECT0: noprofile
// SELECT1: noprofile
// SELECT2-NOT: noprofile
// CHECK: define {{.*}} @boo()
void boo() {}

View File

@ -0,0 +1,28 @@
// RUN: %clang_pgogen -fprofile-function-groups=3 -fprofile-selected-function-group=0 %s -o %t.0.out
// RUN: %clang_pgogen -fprofile-function-groups=3 -fprofile-selected-function-group=1 %s -o %t.1.out
// RUN: %clang_pgogen -fprofile-function-groups=3 -fprofile-selected-function-group=2 %s -o %t.2.out
// RUN: env LLVM_PROFILE_FILE=%t.0.profraw %run %t.0.out
// RUN: env LLVM_PROFILE_FILE=%t.1.profraw %run %t.1.out
// RUN: env LLVM_PROFILE_FILE=%t.2.profraw %run %t.2.out
// RUN: llvm-profdata merge -o %t.profdata %t.*.profraw
// RUN: llvm-profdata show %t.profdata --all-functions | FileCheck %s
int foo(int i) { return 4 * i + 1; }
int bar(int i) { return 4 * i + 2; }
int goo(int i) { return 4 * i + 3; }
int main(int argc, char *argv[]) {
foo(5);
bar(6);
goo(7);
return 0;
}
// Even though we ran this code three times, we expect all counts to be one if
// functions were partitioned into groups correctly.
// CHECK: Counters: 1
// CHECK: Counters: 1
// CHECK: Counters: 1
// CHECK: Counters: 1
// CHECK: Total functions: 4