[llvm] 86fb40d - [ProfileData] Implement contains in SampleProfileNameTable (NFC) (#211995)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 29 12:51:21 PDT 2026


Author: Kazu Hirata
Date: 2026-07-29T12:51:15-07:00
New Revision: 86fb40ddfa3c7cd5c32d758f4b1709d01028a7db

URL: https://github.com/llvm/llvm-project/commit/86fb40ddfa3c7cd5c32d758f4b1709d01028a7db
DIFF: https://github.com/llvm/llvm-project/commit/86fb40ddfa3c7cd5c32d758f4b1709d01028a7db.diff

LOG: [ProfileData] Implement contains in SampleProfileNameTable (NFC) (#211995)

This patch implements contains(StringRef) and contains(uint64_t) in
SampleProfileNameTable and SampleProfileReader to serve symbol
membership queries directly from the reader -- "is this symbol in the
name table?".

Without this patch, users of the sample profile reader, namely
SampleProfileLoader::doInitialization and SampleProfileNameSet, each
construct their own StringSet<> containing all name table entries.
That is, we end up with two instances of StringSet<> with identical
contents.  Since these instances hold their own copies of symbol
strings on the heap, both the constructor and destructor take up a
large portion of compilation time.

This patch teaches SampleProfileReader::contains to directly serve
symbol membership queries.

- For EytzingerSampleProfileNameTable, contains performs binary search
  directly across the three concatenated Eytzinger table spans
  (CSKeys, FlatKeys, and Inlinees) in a cache-friendly manner.

- Other representations of the name table lazily construct an internal
  DenseSet on demand.

This patch updates existing customers to call Reader->contains.

RFC:
https://discourse.llvm.org/t/rfc-faster-sample-profile-loading/90957/8

Assisted-by: Antigravity

Added: 
    

Modified: 
    llvm/include/llvm/ProfileData/SampleProfReader.h
    llvm/lib/Transforms/IPO/SampleProfile.cpp
    llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
    llvm/unittests/ProfileData/SampleProfTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index 0003d06e60373..75cc8545c37dd 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -226,6 +226,8 @@
 #define LLVM_PROFILEDATA_SAMPLEPROFREADER_H
 
 #include "llvm/ADT/Eytzinger.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringSet.h"
@@ -395,9 +397,37 @@ class SampleProfileNameTable {
   virtual size_t size() const = 0;
   bool empty() const { return size() == 0; }
   virtual FunctionId operator[](size_t Idx) const = 0;
+  virtual bool contains(StringRef Key) const {
+    return contains(FunctionId(Key).getHashCode());
+  }
+  virtual bool contains(uint64_t GUID) const {
+    return getOrCreateSet(GUIDSet, *this, GetFunctionIdHash).contains(GUID);
+  }
 
   iterator begin() const { return iterator(this, 0); }
   iterator end() const { return iterator(this, size()); }
+
+protected:
+  mutable std::optional<DenseSet<uint64_t>> GUIDSet;
+
+  static constexpr auto GetFunctionIdHash = [](FunctionId F) {
+    return F.getHashCode();
+  };
+  static constexpr auto GetFunctionIdString = [](FunctionId F) {
+    return F.stringRef();
+  };
+
+  template <typename SetT, typename RangeT, typename ProjT = llvm::identity>
+  static const SetT &getOrCreateSet(std::optional<SetT> &Set,
+                                    const RangeT &Range, ProjT Proj = ProjT()) {
+    if (!Set) {
+      Set.emplace();
+      Set->reserve(Range.size());
+      for (const auto &Item : Range)
+        Set->insert(Proj(Item));
+    }
+    return *Set;
+  }
 };
 
 class LazySampleProfileNameTable final : public SampleProfileNameTable {
@@ -416,10 +446,17 @@ class LazySampleProfileNameTable final : public SampleProfileNameTable {
     return FunctionId(endian::read<uint64_t, unaligned>(
         Start + Idx * sizeof(uint64_t), endianness::little));
   }
+
+  bool contains(uint64_t GUID) const override {
+    ArrayRef<support::ulittle64_t> Table(
+        reinterpret_cast<const support::ulittle64_t *>(Start), Size);
+    return getOrCreateSet(GUIDSet, Table).contains(GUID);
+  }
 };
 
 class StringSampleProfileNameTable final : public SampleProfileNameTable {
   std::vector<FunctionId> Vec;
+  mutable std::optional<DenseSet<StringRef>> NameSet;
 
 public:
   explicit StringSampleProfileNameTable(std::vector<FunctionId> &&Vec)
@@ -433,6 +470,10 @@ class StringSampleProfileNameTable final : public SampleProfileNameTable {
     assert(Idx < Vec.size() && "Index out of bounds");
     return Vec[Idx];
   }
+
+  bool contains(StringRef Key) const override {
+    return getOrCreateSet(NameSet, Vec, GetFunctionIdString).contains(Key);
+  }
 };
 
 class MD5SampleProfileNameTable final : public SampleProfileNameTable {
@@ -454,18 +495,29 @@ class MD5SampleProfileNameTable final : public SampleProfileNameTable {
 
 class EytzingerSampleProfileNameTable final : public SampleProfileNameTable {
   ArrayRef<support::ulittle64_t> Array;
+  std::array<EytzingerTableSpan<support::ulittle64_t>,
+             static_cast<size_t>(EytzingerSpan::NumSpans)>
+      Spans;
 
 public:
   EytzingerSampleProfileNameTable(const support::ulittle64_t *Data,
                                   uint64_t NumCS, uint64_t NumFlat,
                                   uint64_t NumInlinees)
-      : Array(Data, NumCS + NumFlat + NumInlinees) {}
+      : Array(Data, NumCS + NumFlat + NumInlinees),
+        Spans{{{Data, NumCS},
+               {Data + NumCS, NumFlat},
+               {Data + NumCS + NumFlat, NumInlinees}}} {}
 
   size_t size() const override { return Array.size(); }
 
   FunctionId operator[](size_t Idx) const override {
     return FunctionId(Array[Idx]);
   }
+
+  bool contains(uint64_t GUID) const override {
+    return llvm::any_of(Spans,
+                        [&](const auto &Span) { return Span.contains(GUID); });
+  }
 };
 
 class SampleProfileReader {
@@ -630,6 +682,8 @@ class SampleProfileReader {
             SampleProfileNameTable::iterator()};
   }
   virtual bool dumpSectionInfo(raw_ostream &OS = dbgs()) { return false; };
+  virtual bool contains(StringRef Key) const { return false; }
+  virtual bool contains(uint64_t GUID) const { return false; }
 
   /// Return whether names in the profile are all MD5 numbers.
   bool useMD5() const { return ProfileIsMD5; }
@@ -793,6 +847,16 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
     return {NameTable->begin(), NameTable->end()};
   }
 
+  bool contains(StringRef Key) const override {
+    assert(NameTable && "NameTable should be populated before querying");
+    return NameTable->contains(Key);
+  }
+
+  bool contains(uint64_t GUID) const override {
+    assert(NameTable && "NameTable should be populated before querying");
+    return NameTable->contains(GUID);
+  }
+
 protected:
   /// Read a numeric value of type T from the profile.
   ///
@@ -1191,23 +1255,6 @@ class LLVM_ABI SampleProfileReaderGCC : public SampleProfileReader {
   static const uint32_t GCOVTagAFDOFunction = 0xac000000;
 };
 
-/// A helper class that wraps a local set of string names from NameTable.
-class SampleProfileNameSet {
-  const SampleProfileReader &Reader;
-  StringSet<> NamesInProfile;
-
-public:
-  explicit SampleProfileNameSet(const SampleProfileReader &R) : Reader(R) {
-    for (FunctionId Name : Reader.getNameTable())
-      NamesInProfile.insert(Name.stringRef());
-  }
-
-  /// Check if a canonical function name exists in the profile name table.
-  bool contains(StringRef CanonName) const {
-    return NamesInProfile.contains(CanonName);
-  }
-};
-
 } // end namespace sampleprof
 
 } // end namespace llvm

diff  --git a/llvm/lib/Transforms/IPO/SampleProfile.cpp b/llvm/lib/Transforms/IPO/SampleProfile.cpp
index 4234e05430dbf..cb11372183bc8 100644
--- a/llvm/lib/Transforms/IPO/SampleProfile.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfile.cpp
@@ -573,15 +573,6 @@ class SampleProfileLoader final : public SampleProfileLoaderBaseImpl<Function> {
   // all the function symbols defined or declared in current module.
   DenseMap<uint64_t, StringRef> GUIDToFuncNameMap;
 
-  // All the Names used in FunctionSamples including outline function
-  // names, inline instance names and call target names.
-  StringSet<> NamesInProfile;
-  // MD5 version of NamesInProfile. Either NamesInProfile or GUIDsInProfile is
-  // populated, depends on whether the profile uses MD5. Because the name table
-  // generally contains several magnitude more entries than the number of
-  // functions, we do not want to convert all names from one form to another.
-  llvm::DenseSet<uint64_t> GUIDsInProfile;
-
   // For symbol in profile symbol list, whether to regard their profiles
   // to be accurate. It is mainly decided by existance of profile symbol
   // list and -profile-accurate-for-symsinlist flag, but it can be
@@ -1986,19 +1977,8 @@ bool SampleProfileLoader::doInitialization(Module &M,
   // While profile-sample-accurate is on, ignore symbol list.
   ProfAccForSymsInList =
       ProfileAccurateForSymsInList && PSL && !ProfileSampleAccurate;
-  if (ProfAccForSymsInList) {
-    NamesInProfile.clear();
-    GUIDsInProfile.clear();
-    auto NameTable = Reader->getNameTable();
-    if (FunctionSamples::UseMD5) {
-      for (FunctionId Name : NameTable)
-        GUIDsInProfile.insert(Name.getHashCode());
-    } else {
-      for (FunctionId Name : NameTable)
-        NamesInProfile.insert(Name.stringRef());
-    }
+  if (ProfAccForSymsInList)
     CoverageTracker.setProfAccForSymsInList(true);
-  }
 
   if (FAM && !ProfileInlineReplayFile.empty()) {
     ExternalInlineAdvisor = getReplayInlineAdvisor(
@@ -2280,10 +2260,10 @@ bool SampleProfileLoader::runOnFunction(Function &F,
     // but not cold accumulatively...), so the outline function showing up as
     // cold in sampled binary will actually not be cold after current build.
     StringRef CanonName = FunctionSamples::getCanonicalFnName(F);
-    if ((FunctionSamples::UseMD5 &&
-         GUIDsInProfile.count(
-             Function::getGUIDAssumingExternalLinkage(CanonName))) ||
-        (!FunctionSamples::UseMD5 && NamesInProfile.count(CanonName)))
+    if (FunctionSamples::UseMD5
+            ? Reader->contains(
+                  Function::getGUIDAssumingExternalLinkage(CanonName))
+            : Reader->contains(CanonName))
       initialEntryCount = -1;
   }
 

diff  --git a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
index d3aec9d7c363c..ffd6a265dafc8 100644
--- a/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
+++ b/llvm/lib/Transforms/IPO/SampleProfileMatcher.cpp
@@ -771,7 +771,6 @@ void SampleProfileMatcher::findFunctionsWithoutProfile() {
   // TODO: Support MD5 profile.
   if (FunctionSamples::UseMD5)
     return;
-  SampleProfileNameSet NamesInProfile(Reader);
 
   for (auto &F : M) {
     // Skip declarations, as even if the function can be matched, we have
@@ -787,7 +786,7 @@ void SampleProfileMatcher::findFunctionsWithoutProfile() {
     // 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.contains(CanonFName))
+    if (Reader.contains(CanonFName))
       continue;
 
     // For extended binary, non-profiled function symbols are in the profile

diff  --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index 50fe3a1951577..d35427478e129 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -456,6 +456,17 @@ struct SampleProfTest : ::testing::Test {
       if (Samples != nullptr)
         Esamples = Samples->getTotalSamples();
       ASSERT_EQ(I->getValue(), Esamples);
+
+      if (Format == SampleProfileFormat::SPF_Ext_Binary) {
+        ASSERT_TRUE(Reader->contains(I->getKey()));
+        ASSERT_TRUE(Reader->contains(FunctionId(I->getKey()).getHashCode()));
+      }
+    }
+
+    if (Format == SampleProfileFormat::SPF_Ext_Binary) {
+      StringRef FakeSymbol = "non_existent_symbol_for_test";
+      ASSERT_FALSE(Reader->contains(FakeSymbol));
+      ASSERT_FALSE(Reader->contains(FunctionId(FakeSymbol).getHashCode()));
     }
   }
 };


        


More information about the llvm-commits mailing list