Skip to content

Commit

Permalink
[SampleFDO] Compute and report profile staleness metrics
Browse files Browse the repository at this point in the history
When a profile is stale and profile mismatch could happen, the mismatched samples are discarded, so we'd like to compute the mismatch metrics to quantify how stale the profile is, which will suggest user to refresh the profile if the number is high.

Two sets of metrics are introduced here:

 - (Num_of_mismatched_funchash/Total_profiled_funchash), (Samples_of_mismached_func_hash / Samples_of_profiled_function) : Here it leverages the FunctionSamples's checksums attribute which is a feature of pseudo probe. When the source code CFG changes, the function checksums will be different, later sample loader will discard the whole functions' samples, this metrics can show the percentage of samples are discarded due to this.
 -  (Num_of_mismatched_callsite/Total_profiled_callsite), (Samples_of_mismached_callsite / Samples_of_profiled_callsite) : This shows how many mismatching for the callsite location as callsite location mismatch will affect the inlining which is highly correlated with the performance. It goes through all the callsite location in the IR and profile, use the call target name to match, report the num of samples in the profile that doesn't match a IR callsite.

This is implemented in a new class(SampleProfileMatcher) and under a switch("--report-profile-staleness"), we plan to extend it with a fuzzy profile matching feature in the future.

Reviewed By: hoy, wenlei, davidxl

Differential Revision: https://reviews.llvm.org/D136627
  • Loading branch information
wlei-llvm committed Oct 27, 2022
1 parent 63f465b commit d6a0585
Show file tree
Hide file tree
Showing 6 changed files with 619 additions and 0 deletions.
7 changes: 7 additions & 0 deletions llvm/include/llvm/ProfileData/SampleProf.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ struct LineLocation {
uint32_t Discriminator;
};

struct LineLocationHash {
uint64_t operator()(const LineLocation &Loc) const {
return std::hash<std::uint64_t>{}((((uint64_t)Loc.LineOffset) << 32) |
Loc.Discriminator);
}
};

raw_ostream &operator<<(raw_ostream &OS, const LineLocation &Loc);

/// Representation of a single sample record.
Expand Down
154 changes: 154 additions & 0 deletions llvm/lib/Transforms/IPO/SampleProfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ static cl::opt<std::string> SampleProfileRemappingFile(
"sample-profile-remapping-file", cl::init(""), cl::value_desc("filename"),
cl::desc("Profile remapping file loaded by -sample-profile"), cl::Hidden);

static cl::opt<bool> ReportProfileStaleness(
"report-profile-staleness", cl::Hidden, cl::init(false),
cl::desc("Compute and report stale profile statistical metrics."));

static cl::opt<bool> ProfileSampleAccurate(
"profile-sample-accurate", cl::Hidden, cl::init(false),
cl::desc("If the sample profile is accurate, we will mark all un-sampled "
Expand Down Expand Up @@ -414,6 +418,30 @@ using CandidateQueue =
PriorityQueue<InlineCandidate, std::vector<InlineCandidate>,
CandidateComparer>;

// Sample profile matching - fuzzy match.
class SampleProfileMatcher {
Module &M;
SampleProfileReader &Reader;
const PseudoProbeManager *ProbeManager;

// Profile mismatching statstics.
uint64_t TotalProfiledCallsite = 0;
uint64_t NumMismatchedCallsite = 0;
uint64_t MismatchedCallsiteSamples = 0;
uint64_t TotalCallsiteSamples = 0;
uint64_t TotalProfiledFunc = 0;
uint64_t NumMismatchedFuncHash = 0;
uint64_t MismatchedFuncHashSamples = 0;
uint64_t TotalFuncHashSamples = 0;

public:
SampleProfileMatcher(Module &M, SampleProfileReader &Reader,
const PseudoProbeManager *ProbeManager)
: M(M), Reader(Reader), ProbeManager(ProbeManager) {}
void detectProfileMismatch();
void detectProfileMismatch(const Function &F, const FunctionSamples &FS);
};

/// Sample profile pass.
///
/// This pass reads profile data from the file specified by
Expand Down Expand Up @@ -543,6 +571,9 @@ class SampleProfileLoader final
// A pseudo probe helper to correlate the imported sample counts.
std::unique_ptr<PseudoProbeManager> ProbeManager;

// A helper to implement the sample profile matching algorithm.
std::unique_ptr<SampleProfileMatcher> MatchingManager;

private:
const char *getAnnotatedRemarkPassName() const {
return AnnotatedPassName.c_str();
Expand Down Expand Up @@ -2010,9 +2041,129 @@ bool SampleProfileLoader::doInitialization(Module &M,
}
}

if (ReportProfileStaleness) {
MatchingManager =
std::make_unique<SampleProfileMatcher>(M, *Reader, ProbeManager.get());
}

return true;
}

void SampleProfileMatcher::detectProfileMismatch(const Function &F,
const FunctionSamples &FS) {
if (FunctionSamples::ProfileIsProbeBased) {
uint64_t Count = FS.getTotalSamples();
TotalFuncHashSamples += Count;
TotalProfiledFunc++;
if (!ProbeManager->profileIsValid(F, FS)) {
MismatchedFuncHashSamples += Count;
NumMismatchedFuncHash++;
return;
}
}

std::unordered_set<LineLocation, LineLocationHash> MatchedCallsiteLocs;

// Go through all the callsites on the IR and flag the callsite if the target
// name is the same as the one in the profile.
for (auto &BB : F) {
for (auto &I : BB.getInstList()) {
if (!isa<CallBase>(&I) || isa<IntrinsicInst>(&I))
continue;

const auto *CB = dyn_cast<CallBase>(&I);
if (auto &DLoc = I.getDebugLoc()) {
LineLocation IRCallsite = FunctionSamples::getCallSiteIdentifier(DLoc);

StringRef CalleeName;
if (Function *Callee = CB->getCalledFunction())
CalleeName = Callee->getName();

const auto CTM = FS.findCallTargetMapAt(IRCallsite);
const auto CallsiteFS = FS.findFunctionSamplesMapAt(IRCallsite);

// Indirect call case.
if (CalleeName.empty()) {
// Since indirect call does not have the CalleeName, check
// conservatively if callsite in the profile is a callsite location.
// This is to avoid nums of false positive since otherwise all the
// indirect call samples will be reported as mismatching.
if ((CTM && !CTM->empty()) || (CallsiteFS && !CallsiteFS->empty()))
MatchedCallsiteLocs.insert(IRCallsite);
} else {
// Check if the call target name is matched for direct call case.
if ((CTM && CTM->count(CalleeName)) ||
(CallsiteFS && CallsiteFS->count(CalleeName)))
MatchedCallsiteLocs.insert(IRCallsite);
}
}
}
}

auto isInvalidLineOffset = [](uint32_t LineOffset) {
return LineOffset & 0x8000;
};

// Check if there are any callsites in the profile that does not match to any
// IR callsites, those callsite samples will be discarded.
for (auto &I : FS.getBodySamples()) {
const LineLocation &Loc = I.first;
if (isInvalidLineOffset(Loc.LineOffset))
continue;

uint64_t Count = I.second.getSamples();
if (!I.second.getCallTargets().empty()) {
TotalCallsiteSamples += Count;
TotalProfiledCallsite++;
if (!MatchedCallsiteLocs.count(Loc)) {
MismatchedCallsiteSamples += Count;
NumMismatchedCallsite++;
}
}
}

for (auto &I : FS.getCallsiteSamples()) {
const LineLocation &Loc = I.first;
if (isInvalidLineOffset(Loc.LineOffset))
continue;

uint64_t Count = 0;
for (auto &FM : I.second) {
Count += FM.second.getTotalSamples();
}
TotalCallsiteSamples += Count;
TotalProfiledCallsite++;
if (!MatchedCallsiteLocs.count(Loc)) {
MismatchedCallsiteSamples += Count;
NumMismatchedCallsite++;
}
}
}

void SampleProfileMatcher::detectProfileMismatch() {
for (auto &F : M) {
if (F.isDeclaration() || !F.hasFnAttribute("use-sample-profile"))
continue;
FunctionSamples *FS = Reader.getSamplesFor(F);
if (!FS)
continue;
detectProfileMismatch(F, *FS);
}

if (FunctionSamples::ProfileIsProbeBased) {
errs() << "(" << NumMismatchedFuncHash << "/" << TotalProfiledFunc << ")"
<< " of functions' profile are invalid and "
<< " (" << MismatchedFuncHashSamples << "/" << TotalFuncHashSamples
<< ")"
<< " of samples are discarded due to function hash mismatch.\n";
}
errs() << "(" << NumMismatchedCallsite << "/" << TotalProfiledCallsite << ")"
<< " of callsites' profile are invalid and "
<< "(" << MismatchedCallsiteSamples << "/" << TotalCallsiteSamples
<< ")"
<< " of samples are discarded due to callsite location mismatch.\n";
}

bool SampleProfileLoader::runOnModule(Module &M, ModuleAnalysisManager *AM,
ProfileSummaryInfo *_PSI, CallGraph *CG) {
GUIDToFuncNameMapper Mapper(M, *Reader, GUIDToFuncNameMap);
Expand Down Expand Up @@ -2057,6 +2208,9 @@ bool SampleProfileLoader::runOnModule(Module &M, ModuleAnalysisManager *AM,
assert(SymbolMap.count(StringRef()) == 0 &&
"No empty StringRef should be added in SymbolMap");

if (ReportProfileStaleness)
MatchingManager->detectProfileMismatch();

bool retval = false;
for (auto *F : buildFunctionOrder(M, CG)) {
assert(!F->isDeclaration());
Expand Down
14 changes: 14 additions & 0 deletions llvm/test/Transforms/SampleProfile/Inputs/profile-mismatch.prof
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
main:30:0
0: 0
1.1: 0
3: 10 matched:10
4: 10
5: 10 bar_mismatch:10
8: 0
7: foo:10
1: 5
2: 5
bar:10:10
1: 10
matched:10:10
1: 10
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
main:30:0
1: 0
12: 10 matched:10
20: 10 bar:10
13: foo_mismatch:10
1: 10
!CFGChecksum: 4294967295
!CFGChecksum: 844635331715433
bar:10:10
1: 10
!CFGChecksum: 42949671295
matched:10:10
1: 10
!CFGChecksum: 4294967295
Loading

0 comments on commit d6a0585

Please sign in to comment.