[llvm] [SampleFDO] Stale profile call-graph matching (PR #95135)
Lei Wang via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 17 16:28:25 PDT 2024
================
@@ -590,14 +688,237 @@ void SampleProfileMatcher::computeAndReportProfileStaleness() {
}
}
+void SampleProfileMatcher::findNewIRFunctions() {
+ // TODO: Support MD5 profile.
+ if (FunctionSamples::UseMD5)
+ return;
+ StringSet<> NamesInProfile;
+ if (auto NameTable = Reader.getNameTable()) {
+ for (auto Name : *NameTable)
+ NamesInProfile.insert(Name.stringRef());
+ }
+
+ for (auto &F : M) {
+ // Skip declarations, as even if the function can be matched, we have
+ // nothing to do with it.
+ if (F.isDeclaration())
+ continue;
+
+ StringRef CanonFName = FunctionSamples::getCanonicalFnName(F.getName());
+ const auto *FS = getFlattenedSamplesFor(F);
+ if (FS)
+ continue;
+
+ // For extended binary, functions are fully inlined may not be loaded in the
+ // top-level profile, so check the NameTable which has the all symbol names
+ // in profile.
+ if (NamesInProfile.count(CanonFName))
+ continue;
+
+ // For extended binary, non-profiled function symbols are in the profile
+ // symbol list table.
+ if (PSL && PSL->contains(CanonFName))
+ continue;
+
+ LLVM_DEBUG(dbgs() << "Function " << CanonFName
+ << " is not in profile or symbol list table.\n");
+ NewIRFunctions[FunctionId(CanonFName)] = &F;
+ }
+}
+
+bool SampleProfileMatcher::functionMatchesProfileHelper(
+ const Function &IRFunc, const FunctionId &ProfFunc) {
+ // The value is in the range [0, 1]. The bigger the value is, the more similar
+ // two sequences are.
+ float Similarity = 0.0;
+
+ const auto *FSFlattened = getFlattenedSamplesFor(ProfFunc);
+ assert(FSFlattened && "Flattened profile sample is null");
+ // Similarity check may not be reliable if the function is tiny, we use the
+ // number of basic block as a proxy for the function complexity and skip the
+ // matching if it's too small.
+ if (IRFunc.size() < MinFuncCountForCGMatching ||
+ FSFlattened->getBodySamples().size() < MinFuncCountForCGMatching)
+ return false;
+
+ // For probe-based function, we first trust the checksum info. If the checksum
+ // doesn't match, we continue checking for similarity.
+ if (FunctionSamples::ProfileIsProbeBased) {
+ const auto *FuncDesc = ProbeManager->getDesc(IRFunc);
+ if (FuncDesc &&
+ !ProbeManager->profileIsHashMismatched(*FuncDesc, *FSFlattened)) {
+ LLVM_DEBUG(dbgs() << "The checksums for " << IRFunc.getName()
+ << "(IR) and " << ProfFunc << "(Profile) match.\n");
+
+ return true;
+ }
+ }
+
+ AnchorMap IRAnchors;
+ findIRAnchors(IRFunc, IRAnchors);
+ AnchorMap ProfileAnchors;
+ findProfileAnchors(*FSFlattened, ProfileAnchors);
+
+ AnchorList FilteredIRAnchorsList;
+ AnchorList FilteredProfileAnchorList;
+ getFilteredAnchorList(IRAnchors, ProfileAnchors, FilteredIRAnchorsList,
+ FilteredProfileAnchorList);
+
+ // Similarly skip the matching if the num of anchors is not enough.
+ if (FilteredIRAnchorsList.size() < MinCallCountForCGMatching ||
+ FilteredProfileAnchorList.size() < MinCallCountForCGMatching)
+ return false;
+
+ // Use the diff algorithm to find the LCS between IR and profile.
+
+ // Don't recursively match the callee function to avoid infinite matching,
+ // callee functions will be handled later since it's processed in top-down
+ // order .
+ LocToLocMap MatchedAnchors = longestCommonSequence(
+ FilteredIRAnchorsList, FilteredProfileAnchorList, false);
+
+ Similarity =
+ static_cast<float>(MatchedAnchors.size()) * 2 /
+ (FilteredIRAnchorsList.size() + FilteredProfileAnchorList.size());
+
+ LLVM_DEBUG(dbgs() << "The similarity between " << IRFunc.getName()
+ << "(IR) and " << ProfFunc << "(profile) is "
+ << format("%.2f", Similarity) << "\n");
+ assert((Similarity >= 0 && Similarity <= 1.0) &&
+ "Similarity value should be in [0, 1]");
+ return Similarity * 100 > FuncProfileSimilarityThreshold;
+}
+
+// If FindMatchedProfileOnly is set to true, only use the processed function
+// results. This is used for skipping the repeated recursive matching.
+bool SampleProfileMatcher::functionMatchesProfile(Function &IRFunc,
+ const FunctionId &ProfFunc,
+ bool FindMatchedProfileOnly) {
+ auto R = FuncToProfileNameMap.find({&IRFunc, ProfFunc});
+ if (R != FuncToProfileNameMap.end())
+ return R->second;
+
+ if (FindMatchedProfileOnly)
+ return false;
+
+ bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
+ FuncToProfileNameMap[{&IRFunc, ProfFunc}] = Matched;
+ if (Matched) {
+ ProfileNameToFuncMap[ProfFunc] = &IRFunc;
+ LLVM_DEBUG(dbgs() << "Function:" << IRFunc.getName()
+ << " matches profile:" << ProfFunc << "\n");
+ }
+
+ return Matched;
+}
+
+void SampleProfileMatcher::updateProfileWithNewName(
+ FunctionSamples &FuncProfile) {
+ auto FindNewMatch =
+ [&](const FunctionId &ProfileName,
+ std::vector<std::pair<FunctionId, FunctionId>> &MatchResult,
+ [[maybe_unused]] const FunctionId &CallerName) {
+ auto P = ProfileNameToFuncMap.find(ProfileName);
+ if (P != ProfileNameToFuncMap.end()) {
+ FunctionId IRCallee(P->second->getName());
+ assert(IRCallee != ProfileName &&
+ "New callee symbol is not a new function");
+ LLVM_DEBUG(dbgs()
+ << "Profile name is updated from " << ProfileName << " to "
+ << IRCallee << " under caller: " << CallerName << "\n");
+ MatchResult.emplace_back(IRCallee, ProfileName);
+ }
+ };
+
+ // Update non-inline callees.
+ for (auto &BS : const_cast<BodySampleMap &>(FuncProfile.getBodySamples())) {
+ // New function to old function pairs used to update the CallTargetMap.
+ std::vector<std::pair<FunctionId, FunctionId>> MatchResult;
+ SampleRecord::CallTargetMap &CTM =
+ const_cast<SampleRecord::CallTargetMap &>(BS.second.getCallTargets());
+ for (const auto &TS : CTM)
+ FindNewMatch(TS.first, MatchResult, FuncProfile.getFunction());
+ // Update the CallTargetMap.
+ for (const auto &P : MatchResult) {
+ uint64_t Samples = CTM[P.second];
+ if (ReportProfileStaleness || PersistProfileStaleness)
+ NumRecoveredUnusedSamples += Samples;
+ CTM[P.first] = Samples;
+ CTM.erase(P.second);
+ }
+ }
+
+ // Update inline callees recursively.
+ for (auto &CM :
+ const_cast<CallsiteSampleMap &>(FuncProfile.getCallsiteSamples())) {
+ auto &CalleeMap = CM.second;
+ // New function to old function pairs used to update the CallsiteSampleMap.
+ std::vector<std::pair<FunctionId, FunctionId>> MatchResult;
+ for (auto &CS : CalleeMap) {
+ FindNewMatch(CS.second.getFunction(), MatchResult,
+ FuncProfile.getFunction());
+ updateProfileWithNewName(CS.second);
+ }
+ // Update the CalleeMap using the new name and remove the old entry.
+ for (auto &P : MatchResult) {
+ assert(P.first != P.second &&
+ "Renamed function name should be different from the old map key");
+ FunctionSamples &FS = CalleeMap[P.second];
+ if (ReportProfileStaleness || PersistProfileStaleness)
+ NumRecoveredUnusedSamples += FS.getTotalSamples();
+ FS.setFunction(P.first);
+ CalleeMap[P.first] = FS;
+ CalleeMap.erase(P.second);
+ }
+ }
+}
+
+void SampleProfileMatcher::updateProfilesAndSymbolMap() {
+ if (ProfileNameToFuncMap.empty())
+ return;
+ for (auto &P : Reader.getProfiles())
+ updateProfileWithNewName(P.second);
+
+ // Add the new function to the SymbolMap, which will be used in
+ // SampleLoader.
+ for (auto &I : ProfileNameToFuncMap) {
+ assert(I.second && "New function is null");
+ SymbolMap->emplace(FunctionId(I.second->getName()), I.second);
+ }
+}
+
+std::vector<Function *> SampleProfileMatcher::buildTopDownFuncOrder() {
+ std::vector<Function *> FunctionOrderList;
----------------
wlei-llvm wrote:
The wrapper function is for sharing the code with the same features in `SampleLoader`(see https://github.com/llvm/llvm-project/blob/main/llvm/lib/Transforms/IPO/SampleProfile.cpp#L1934-L1942). And I searched around, seems the SCC API doesn't support directly top-down order (see https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Analysis/LazyCallGraph.h#L973 that there is only `postorder_ref_sccs` but no `preorder_ref_sccs` things)
https://github.com/llvm/llvm-project/pull/95135
More information about the llvm-commits
mailing list