[llvm] [SampleFDO] Stale profile call-graph matching (PR #95135)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 24 13:32:27 PDT 2024


================
@@ -590,14 +720,160 @@ 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 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 profile symbol list.\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");
+  // The check for similarity or checksum 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 /* Match unused functions */);
+
+  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 = FuncProfileMatchCache.find({&IRFunc, ProfFunc});
+  if (R != FuncProfileMatchCache.end())
+    return R->second;
+
+  if (FindMatchedProfileOnly)
+    return false;
+
+  bool Matched = functionMatchesProfileHelper(IRFunc, ProfFunc);
+  FuncProfileMatchCache[{&IRFunc, ProfFunc}] = Matched;
+  if (Matched) {
+    FuncToProfileNameMap[&IRFunc] = ProfFunc;
+    LLVM_DEBUG(dbgs() << "Function:" << IRFunc.getName()
+                      << " matches profile:" << ProfFunc << "\n");
+  }
+
+  return Matched;
+}
+
 void SampleProfileMatcher::runOnModule() {
   ProfileConverter::flattenProfile(Reader.getProfiles(), FlattenedProfiles,
                                    FunctionSamples::ProfileIsCS);
-  for (auto &F : M) {
-    if (skipProfileForFunction(F))
+  if (SalvageUnusedProfile)
+    findNewIRFunctions();
+
+  // Process the matching in top-down order so that the caller matching result
+  // can be used to the callee matching.
+  std::vector<Function *> TopDownFunctionList;
+  TopDownFunctionList.reserve(M.size());
+  buildTopDownFuncOrder(CG, TopDownFunctionList);
+  for (auto *F : TopDownFunctionList) {
+    if (skipProfileForFunction(*F))
       continue;
-    runOnFunction(F);
+    runOnFunction(*F);
   }
+
+  // Update the data in SampleLoader.
+  if (SalvageUnusedProfile)
+    for (auto &I : FuncToProfileNameMap) {
+      assert(I.first && "New function is null");
+      FunctionId FuncName(I.first->getName());
+      FuncNameToProfNameMap->emplace(FuncName, I.second);
+      // We need to remove the old entry to avoid duplicating the function
+      // processing.
+      SymbolMap->erase(FuncName);
+      SymbolMap->emplace(I.second, I.first);
+    }
----------------
WenleiHe wrote:

This looks a lot cleaner than updating everything for all function/inlinee samples with name mapping :) 

https://github.com/llvm/llvm-project/pull/95135


More information about the llvm-commits mailing list