[llvm] [BOLT] Reduce NameResolver memory usage during file object discovery (PR #212625)

Rafael Auler via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 17:54:51 PDT 2026


https://github.com/rafaelauler updated https://github.com/llvm/llvm-project/pull/212625

>From 4af90c760b90eb838722b5fee3b70d98b56126bd Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Fri, 24 Jul 2026 18:16:30 -0700
Subject: [PATCH 1/2] [BOLT] Reduce NameResolver memory usage during file
 object discovery

Summary:
NameResolver used a StringMap<uint64_t> to count duplicate names. StringMap
owns its keys, so every uniquify()/getUniquifiedNameCount() query allocated a
full copy of each (potentially large, mangled) symbol name. During
discoverFileObjects on a large binary, this string-key duplication accounted
for ~2 GB (1 to 2% of RSS) of allocations in
StringMap::try_emplace_with_hash -> StringMapEntry::create -> allocateWithKey.

Replace the StringMap with a DenseMap<pair<uint64_t,uint64_t>, uint64_t> keyed
by a 128-bit xxh3 hash of the name. No string is ever stored: each distinct
name costs a fixed-size entry regardless of length. A 128-bit hash makes
collisions effectively impossible, so the per-name counts (and therefore the
generated 'Name/ID' unique names) are identical to the string-keyed map and
remain reproducible to match profile (fdata) names.

Also clear the map at the end of discoverFileObjects, since the resolver is not
needed afterwards; all NR.uniquify()/getUniquifiedNameCount() calls occur
within that function's dynamic extent (including processRelocations and
registerFragments).
---
 bolt/include/bolt/Utils/NameResolver.h | 31 ++++++++++++++++++++------
 bolt/lib/Rewrite/RewriteInstance.cpp   |  4 ++++
 2 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/bolt/include/bolt/Utils/NameResolver.h b/bolt/include/bolt/Utils/NameResolver.h
index 9719ce1297a7f..4924c2c4b432c 100644
--- a/bolt/include/bolt/Utils/NameResolver.h
+++ b/bolt/include/bolt/Utils/NameResolver.h
@@ -13,26 +13,39 @@
 #ifndef BOLT_UTILS_NAME_RESOLVER_H
 #define BOLT_UTILS_NAME_RESOLVER_H
 
-#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/Support/xxhash.h"
 
 namespace llvm {
 namespace bolt {
 
 class NameResolver {
-  /// Track the number of duplicate names.
-  StringMap<uint64_t> Counters;
+  /// Track the number of duplicate names, keyed by a 128-bit hash of the name
+  /// rather than by the name itself. Storing hashes instead of the full strings
+  /// avoids duplicating potentially large (mangled) symbol names, which is a
+  /// significant source of memory use while processing the symbol table. Using
+  /// a 128-bit hash makes collisions effectively impossible, so the counts (and
+  /// therefore the generated unique names) are identical to a string-keyed map
+  /// and remain reproducible to match profile (fdata) names.
+  DenseMap<std::pair<uint64_t, uint64_t>, uint64_t> Counters;
 
   /// Character guaranteed not to be used by any "native" name passed to
   /// uniquify() function.
   static constexpr char Sep = '/';
 
+  /// Return the map key used to track occurrences of \p Name.
+  static std::pair<uint64_t, uint64_t> getKey(StringRef Name) {
+    const XXH128_hash_t Hash = llvm::xxh3_128bits(
+        {reinterpret_cast<const uint8_t *>(Name.data()), Name.size()});
+    return {Hash.low64, Hash.high64};
+  }
+
 public:
   /// Return the number of uniquified versions of a given \p Name.
   uint64_t getUniquifiedNameCount(StringRef Name) const {
-    if (Counters.contains(Name))
-      return Counters.at(Name);
-    return 0;
+    return Counters.lookup(getKey(Name));
   }
 
   /// Return unique version of the \p Name in the form "Name<Sep><ID>".
@@ -43,10 +56,14 @@ class NameResolver {
   /// Register new version of \p Name and return unique version in the form
   /// "Name<Sep><Number>".
   std::string uniquify(StringRef Name) {
-    const uint64_t ID = ++Counters[Name];
+    const uint64_t ID = ++Counters[getKey(Name)];
     return getUniqueName(Name, ID);
   }
 
+  /// Release the memory used to track name occurrences. Call once no more names
+  /// need to be uniquified (e.g. after file object discovery is complete).
+  void clear() { Counters.clear(); }
+
   /// For uniquified \p Name, return the original form (that may no longer be
   /// unique).
   static StringRef restore(StringRef Name) {
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 86b078e1de177..acd361097ef39 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -1451,6 +1451,10 @@ void RewriteInstance::discoverFileObjects() {
   FileSymRefs.clear();
 
   discoverBOLTReserved();
+
+  // The name resolver is only needed while discovering and disambiguating file
+  // objects. Release its memory now that all names have been uniquified.
+  NR.clear();
 }
 
 void RewriteInstance::discoverBOLTReserved() {

>From 6e44afa478bde3c6fd109513dbcbf7a650d6a7f7 Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Tue, 28 Jul 2026 17:54:29 -0700
Subject: [PATCH 2/2] Don't use ADT convenience xxh3_128bits variant

---
 bolt/include/bolt/Utils/NameResolver.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bolt/include/bolt/Utils/NameResolver.h b/bolt/include/bolt/Utils/NameResolver.h
index 4924c2c4b432c..34b91f0bf401b 100644
--- a/bolt/include/bolt/Utils/NameResolver.h
+++ b/bolt/include/bolt/Utils/NameResolver.h
@@ -38,7 +38,7 @@ class NameResolver {
   /// Return the map key used to track occurrences of \p Name.
   static std::pair<uint64_t, uint64_t> getKey(StringRef Name) {
     const XXH128_hash_t Hash = llvm::xxh3_128bits(
-        {reinterpret_cast<const uint8_t *>(Name.data()), Name.size()});
+        reinterpret_cast<const uint8_t *>(Name.data()), Name.size());
     return {Hash.low64, Hash.high64};
   }
 



More information about the llvm-commits mailing list