[llvm] [ProfileData] Implement contains in SampleProfileNameTable (NFC) (PR #211995)

Kazu Hirata via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 21:15:12 PDT 2026


https://github.com/kazutakahirata updated https://github.com/llvm/llvm-project/pull/211995

>From 31229b863a26e9f0267e28e160e5a653a65eecf3 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Fri, 24 Jul 2026 20:13:02 -0700
Subject: [PATCH 1/3] [ProfileData] Implement contains in
 SampleProfileNameTable (NFC)

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
---
 .../llvm/ProfileData/SampleProfReader.h       | 88 +++++++++++++++----
 llvm/lib/Transforms/IPO/SampleProfile.cpp     | 30 ++-----
 .../Transforms/IPO/SampleProfileMatcher.cpp   |  3 +-
 llvm/unittests/ProfileData/SampleProfTest.cpp | 11 +++
 4 files changed, 87 insertions(+), 45 deletions(-)

diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index 0003d06e60373..257bb3996927f 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,14 +397,39 @@ 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 = 0;
 
   iterator begin() const { return iterator(this, 0); }
   iterator end() const { return iterator(this, size()); }
+
+protected:
+  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 {
   const uint8_t *Start = nullptr;
   size_t Size = 0;
+  mutable std::optional<DenseSet<uint64_t>> GUIDSet;
 
 public:
   LazySampleProfileNameTable(const uint8_t *Start, size_t Size)
@@ -416,10 +443,18 @@ 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;
+  mutable std::optional<DenseSet<uint64_t>> GUIDSet;
 
 public:
   explicit StringSampleProfileNameTable(std::vector<FunctionId> &&Vec)
@@ -433,10 +468,19 @@ 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);
+  }
+
+  bool contains(uint64_t GUID) const override {
+    return getOrCreateSet(GUIDSet, Vec, GetFunctionIdHash).contains(GUID);
+  }
 };
 
 class MD5SampleProfileNameTable final : public SampleProfileNameTable {
   std::vector<FunctionId> Vec;
+  mutable std::optional<DenseSet<uint64_t>> MD5Set;
 
 public:
   explicit MD5SampleProfileNameTable(std::vector<FunctionId> &&Vec)
@@ -450,22 +494,37 @@ class MD5SampleProfileNameTable final : public SampleProfileNameTable {
     assert(Idx < Vec.size() && "Index out of bounds");
     return Vec[Idx];
   }
+
+  bool contains(uint64_t GUID) const override {
+    return getOrCreateSet(MD5Set, Vec, GetFunctionIdHash).contains(GUID);
+  }
 };
 
 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 +689,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 +854,14 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
     return {NameTable->begin(), NameTable->end()};
   }
 
+  bool contains(StringRef Key) const override {
+    return NameTable && NameTable->contains(Key);
+  }
+
+  bool contains(uint64_t GUID) const override {
+    return NameTable && NameTable->contains(GUID);
+  }
+
 protected:
   /// Read a numeric value of type T from the profile.
   ///
@@ -1191,23 +1260,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()));
     }
   }
 };

>From f05bf6e080fa917d1a7eb673f4566e1eee3a22fc Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Mon, 27 Jul 2026 18:27:47 -0700
Subject: [PATCH 2/3] Address comments.

---
 .../include/llvm/ProfileData/SampleProfReader.h | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index 257bb3996927f..d26199d4fec71 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -400,12 +400,16 @@ class SampleProfileNameTable {
   virtual bool contains(StringRef Key) const {
     return contains(FunctionId(Key).getHashCode());
   }
-  virtual bool contains(uint64_t GUID) const = 0;
+  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();
   };
@@ -429,7 +433,6 @@ class SampleProfileNameTable {
 class LazySampleProfileNameTable final : public SampleProfileNameTable {
   const uint8_t *Start = nullptr;
   size_t Size = 0;
-  mutable std::optional<DenseSet<uint64_t>> GUIDSet;
 
 public:
   LazySampleProfileNameTable(const uint8_t *Start, size_t Size)
@@ -454,7 +457,6 @@ class LazySampleProfileNameTable final : public SampleProfileNameTable {
 class StringSampleProfileNameTable final : public SampleProfileNameTable {
   std::vector<FunctionId> Vec;
   mutable std::optional<DenseSet<StringRef>> NameSet;
-  mutable std::optional<DenseSet<uint64_t>> GUIDSet;
 
 public:
   explicit StringSampleProfileNameTable(std::vector<FunctionId> &&Vec)
@@ -472,15 +474,10 @@ class StringSampleProfileNameTable final : public SampleProfileNameTable {
   bool contains(StringRef Key) const override {
     return getOrCreateSet(NameSet, Vec, GetFunctionIdString).contains(Key);
   }
-
-  bool contains(uint64_t GUID) const override {
-    return getOrCreateSet(GUIDSet, Vec, GetFunctionIdHash).contains(GUID);
-  }
 };
 
 class MD5SampleProfileNameTable final : public SampleProfileNameTable {
   std::vector<FunctionId> Vec;
-  mutable std::optional<DenseSet<uint64_t>> MD5Set;
 
 public:
   explicit MD5SampleProfileNameTable(std::vector<FunctionId> &&Vec)
@@ -494,10 +491,6 @@ class MD5SampleProfileNameTable final : public SampleProfileNameTable {
     assert(Idx < Vec.size() && "Index out of bounds");
     return Vec[Idx];
   }
-
-  bool contains(uint64_t GUID) const override {
-    return getOrCreateSet(MD5Set, Vec, GetFunctionIdHash).contains(GUID);
-  }
 };
 
 class EytzingerSampleProfileNameTable final : public SampleProfileNameTable {

>From f621121a0f1a2570d0c49cc3725419f217cf0556 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Tue, 28 Jul 2026 21:09:55 -0700
Subject: [PATCH 3/3] Address comments.

---
 llvm/include/llvm/ProfileData/SampleProfReader.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index d26199d4fec71..75cc8545c37dd 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -848,11 +848,13 @@ class LLVM_ABI SampleProfileReaderBinary : public SampleProfileReader {
   }
 
   bool contains(StringRef Key) const override {
-    return NameTable && NameTable->contains(Key);
+    assert(NameTable && "NameTable should be populated before querying");
+    return NameTable->contains(Key);
   }
 
   bool contains(uint64_t GUID) const override {
-    return NameTable && NameTable->contains(GUID);
+    assert(NameTable && "NameTable should be populated before querying");
+    return NameTable->contains(GUID);
   }
 
 protected:



More information about the llvm-commits mailing list