[llvm] [SampleProfile] Support MD5-based ProfileSymbolList (PR #210235)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 16 20:50:08 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-pgo
Author: Kazu Hirata (kazutakahirata)
<details>
<summary>Changes</summary>
This patch speeds up sample profile loading by introducing an
MD5-based ProfileSymbolList (cold symbol list) in extensible binary
profiles.
The sample profile loader spends about a third of its time on loading
and decoding the symbol names in the Profile Symbol List section, yet
we use them only for membership checking purposes via
ProfileSymbolList::contains.
This patch teaches the writer to emit the section as an array of
64-bit GUIDs in the Eytzinger layout. The reader checks the
SecFlagMD5 section flag and sets up an EytzingerTableSpan
pointing into the mmap memory. This achieves both space efficiency
and runtime efficiency.
The new flag, md5-prof-sym-list, is off by default for now.
Profile merging is supported only from strings to an MD5-based
Eytzinger array.
RFC:
https://discourse.llvm.org/t/rfc-faster-sample-profile-loading/90957/7
Assisted-by: Antitygravity
---
Full diff: https://github.com/llvm/llvm-project/pull/210235.diff
7 Files Affected:
- (modified) llvm/include/llvm/ProfileData/SampleProf.h (+37-2)
- (modified) llvm/include/llvm/ProfileData/SampleProfReader.h (+3-1)
- (modified) llvm/include/llvm/ProfileData/SampleProfWriter.h (+3)
- (modified) llvm/lib/ProfileData/SampleProfReader.cpp (+29-2)
- (modified) llvm/lib/ProfileData/SampleProfWriter.cpp (+36)
- (modified) llvm/test/tools/llvm-profdata/profile-symbol-list.test (+6)
- (modified) llvm/unittests/ProfileData/SampleProfTest.cpp (+28)
``````````diff
diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h
index b9eabcd661549..5490d1f448b3d 100644
--- a/llvm/include/llvm/ProfileData/SampleProf.h
+++ b/llvm/include/llvm/ProfileData/SampleProf.h
@@ -16,6 +16,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/Eytzinger.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
@@ -208,6 +209,10 @@ enum class SecNameTableFlags : uint32_t {
// the suffix when doing profile matching when seeing the flag.
SecFlagUniqSuffix = (1 << 2)
};
+enum class SecProfileSymbolListFlags : uint32_t {
+ SecFlagInValid = 0,
+ SecFlagMD5 = (1 << 0)
+};
enum class SecProfSummaryFlags : uint32_t {
SecFlagInValid = 0,
/// SecFlagPartial means the profile is for common/shared code.
@@ -254,6 +259,9 @@ static inline void verifySecFlag(SecType Type, SecFlagType Flag) {
case SecNameTable:
IsFlagLegal = std::is_same<SecNameTableFlags, SecFlagType>();
break;
+ case SecProfileSymbolList:
+ IsFlagLegal = std::is_same<SecProfileSymbolListFlags, SecFlagType>();
+ break;
case SecProfSummary:
IsFlagLegal = std::is_same<SecProfSummaryFlags, SecFlagType>();
break;
@@ -1688,19 +1696,45 @@ class ProfileSymbolList {
Syms.insert(Name.copy(Allocator));
}
- bool contains(StringRef Name) { return Syms.count(Name); }
+ bool contains(StringRef Name) const {
+ return Syms.count(Name) || ColdGUIDTable.contains(llvm::MD5Hash(Name));
+ }
void merge(const ProfileSymbolList &List) {
+ assert(List.ColdGUIDTable.empty() &&
+ "Merging pre-hashed MD5 ProfileSymbolList not yet implemented");
for (auto Sym : List.Syms)
add(Sym, true);
}
- unsigned size() { return Syms.size(); }
+ unsigned size() const {
+ assert((ColdGUIDTable.empty() || Syms.empty()) &&
+ "Mixed string/GUID ProfileSymbolList size not yet implemented");
+ return Syms.size() + ColdGUIDTable.size();
+ }
void reserve(size_t Size) { Syms.reserve(Size); }
void setToCompress(bool TC) { ToCompress = TC; }
bool toCompress() { return ToCompress; }
+ std::vector<uint64_t> collectGUIDs() const {
+ assert(ColdGUIDTable.empty() &&
+ "Collecting GUIDs from existing MD5 table not yet implemented");
+ std::vector<uint64_t> Keys;
+ Keys.reserve(Syms.size());
+ llvm::append_range(Keys, llvm::map_range(Syms, llvm::MD5Hash));
+ llvm::sort(Keys);
+ Keys.erase(llvm::unique(Keys), Keys.end());
+ return Keys;
+ }
+
+ void setColdGUIDTable(EytzingerTableSpan<support::ulittle64_t> Table) {
+ ColdGUIDTable = Table;
+ }
+ EytzingerTableSpan<support::ulittle64_t> getColdGUIDTable() const {
+ return ColdGUIDTable;
+ }
+
LLVM_ABI std::error_code read(const uint8_t *Data, uint64_t ListSize);
LLVM_ABI std::error_code write(raw_ostream &OS);
LLVM_ABI void dump(raw_ostream &OS = dbgs()) const;
@@ -1711,6 +1745,7 @@ class ProfileSymbolList {
// list is read from an existing profile.
bool ToCompress = false;
DenseSet<StringRef> Syms;
+ EytzingerTableSpan<support::ulittle64_t> ColdGUIDTable;
BumpPtrAllocator Allocator;
};
diff --git a/llvm/include/llvm/ProfileData/SampleProfReader.h b/llvm/include/llvm/ProfileData/SampleProfReader.h
index 890c3db244758..ceac6c3d78cf0 100644
--- a/llvm/include/llvm/ProfileData/SampleProfReader.h
+++ b/llvm/include/llvm/ProfileData/SampleProfReader.h
@@ -1032,7 +1032,9 @@ class LLVM_ABI SampleProfileReaderExtBinaryBase
SampleProfileMap &Profiles);
std::error_code readNameTableSec(bool IsMD5, bool FixedLengthMD5);
std::error_code readCSNameTableSec();
- std::error_code readProfileSymbolList();
+ std::error_code readProfileSymbolList(bool IsMD5);
+ std::error_code readStringBasedProfileSymbolList();
+ std::error_code readMD5ProfileSymbolList();
std::error_code readHeader() override;
std::error_code verifySPMagic(uint64_t Magic) override = 0;
diff --git a/llvm/include/llvm/ProfileData/SampleProfWriter.h b/llvm/include/llvm/ProfileData/SampleProfWriter.h
index 8756c038ff17c..58b533208875b 100644
--- a/llvm/include/llvm/ProfileData/SampleProfWriter.h
+++ b/llvm/include/llvm/ProfileData/SampleProfWriter.h
@@ -12,6 +12,7 @@
#ifndef LLVM_PROFILEDATA_SAMPLEPROFWRITER_H
#define LLVM_PROFILEDATA_SAMPLEPROFWRITER_H
+#include "llvm/ADT/Eytzinger.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/ProfileSummary.h"
@@ -416,6 +417,8 @@ class LLVM_ABI SampleProfileWriterExtBinaryBase
std::error_code writeNameTableSection(const SampleProfileMap &ProfileMap);
std::error_code writeFuncOffsetTable();
std::error_code writeProfileSymbolListSection();
+ std::error_code writeStringBasedProfileSymbolListSection();
+ std::error_code writeMD5ProfileSymbolListSection();
SectionLayout SecLayout = DefaultLayout;
// Specifiy the order of sections in section header table. Note
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index b28b3406710d0..80c41123eb5b3 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -909,7 +909,8 @@ std::error_code SampleProfileReaderExtBinaryBase::readOneSection(
break;
}
case SecProfileSymbolList:
- if (std::error_code EC = readProfileSymbolList())
+ if (std::error_code EC = readProfileSymbolList(
+ hasSecFlag(Entry, SecProfileSymbolListFlags::SecFlagMD5)))
return EC;
break;
default:
@@ -1135,7 +1136,29 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles() {
return sampleprof_error::success;
}
-std::error_code SampleProfileReaderExtBinaryBase::readProfileSymbolList() {
+std::error_code
+SampleProfileReaderExtBinaryBase::readProfileSymbolList(bool IsMD5) {
+ if (IsMD5)
+ return readMD5ProfileSymbolList();
+ return readStringBasedProfileSymbolList();
+}
+
+std::error_code SampleProfileReaderExtBinaryBase::readMD5ProfileSymbolList() {
+ size_t Size = End - Data;
+ if (Size % sizeof(uint64_t) != 0)
+ return sampleprof_error::truncated;
+ const auto *Table = reinterpret_cast<const support::ulittle64_t *>(Data);
+ size_t NumEntries = Size / sizeof(uint64_t);
+ if (!ProfSymList)
+ ProfSymList = std::make_unique<ProfileSymbolList>();
+ ProfSymList->setColdGUIDTable(
+ EytzingerTableSpan<support::ulittle64_t>(Table, NumEntries));
+ Data = End;
+ return sampleprof_error::success;
+}
+
+std::error_code
+SampleProfileReaderExtBinaryBase::readStringBasedProfileSymbolList() {
if (!ProfSymList)
ProfSymList = std::make_unique<ProfileSymbolList>();
@@ -1589,6 +1612,10 @@ static std::string getSecFlagsStr(const SecHdrTableEntry &Entry) {
if (hasSecFlag(Entry, SecFuncMetadataFlags::SecFlagHasAttribute))
Flags.append("attr,");
break;
+ case SecProfileSymbolList:
+ if (hasSecFlag(Entry, SecProfileSymbolListFlags::SecFlagMD5))
+ Flags.append("md5,");
+ break;
default:
break;
}
diff --git a/llvm/lib/ProfileData/SampleProfWriter.cpp b/llvm/lib/ProfileData/SampleProfWriter.cpp
index 62bdf5343a2f0..2ed81b89d43e1 100644
--- a/llvm/lib/ProfileData/SampleProfWriter.cpp
+++ b/llvm/lib/ProfileData/SampleProfWriter.cpp
@@ -49,6 +49,11 @@ static cl::opt<uint64_t> RequestedVersion(
"sample-profile-format-version", cl::init(DefaultVersion), cl::Hidden,
cl::desc("Format version to write for extensible binary profiles"));
+static cl::opt<bool>
+ WriteMD5ProfSymList("md5-prof-sym-list", cl::init(false), cl::Hidden,
+ cl::desc("Write ProfileSymbolList (Cold Symbols) as "
+ "64-bit MD5 hashes in Eytzinger layout"));
+
namespace llvm {
namespace support {
namespace endian {
@@ -422,6 +427,16 @@ std::error_code SampleProfileWriterExtBinaryBase::writeCSNameTableSection() {
std::error_code
SampleProfileWriterExtBinaryBase::writeProfileSymbolListSection() {
+ if (WriteMD5ProfSymList)
+ return writeMD5ProfileSymbolListSection();
+ return writeStringBasedProfileSymbolListSection();
+}
+
+std::error_code
+SampleProfileWriterExtBinaryBase::writeStringBasedProfileSymbolListSection() {
+ assert((!ProfSymList || ProfSymList->getColdGUIDTable().empty()) &&
+ "Writing string-based ProfileSymbolListSection from MD5 table "
+ "not yet implemented");
if (ProfSymList && ProfSymList->size() > 0)
if (std::error_code EC = ProfSymList->write(*OutputStream))
return EC;
@@ -429,6 +444,25 @@ SampleProfileWriterExtBinaryBase::writeProfileSymbolListSection() {
return sampleprof_error::success;
}
+std::error_code
+SampleProfileWriterExtBinaryBase::writeMD5ProfileSymbolListSection() {
+ if (!ProfSymList || ProfSymList->size() == 0)
+ return sampleprof_error::success;
+ assert(ProfSymList->getColdGUIDTable().empty() &&
+ "Writing MD5 ProfileSymbolListSection from existing MD5 "
+ "table not yet implemented");
+
+ auto &OS = *OutputStream;
+ std::vector<uint64_t> Keys = ProfSymList->collectGUIDs();
+
+ auto Table =
+ llvm::EytzingerTable<support::ulittle64_t>::create(std::move(Keys));
+
+ OS.write(reinterpret_cast<const char *>(Table.data()),
+ Table.size() * sizeof(support::ulittle64_t));
+ return sampleprof_error::success;
+}
+
std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
SecType Type, uint32_t LayoutIdx, const SampleProfileMap &ProfileMap) {
// The setting of SecFlagCompress should happen before markSectionStart.
@@ -448,6 +482,8 @@ std::error_code SampleProfileWriterExtBinaryBase::writeOneSection(
if (Type == SecProfSummary && ExtBinaryWriteVTableTypeProf)
addSectionFlag(SecProfSummary,
SecProfSummaryFlags::SecFlagHasVTableTypeProf);
+ if (Type == SecProfileSymbolList && WriteMD5ProfSymList)
+ addSectionFlag(SecProfileSymbolList, SecProfileSymbolListFlags::SecFlagMD5);
uint64_t SectionStart = markSectionStart(Type, LayoutIdx);
switch (Type) {
diff --git a/llvm/test/tools/llvm-profdata/profile-symbol-list.test b/llvm/test/tools/llvm-profdata/profile-symbol-list.test
index 6845531066c76..e19fd044a02f3 100644
--- a/llvm/test/tools/llvm-profdata/profile-symbol-list.test
+++ b/llvm/test/tools/llvm-profdata/profile-symbol-list.test
@@ -8,6 +8,12 @@
; NOSYMLIST: ProfileSymbolListSection {{.*}} Size: 0
+;; Verify that -md5-prof-sym-list records the md5 section flag for ProfileSymbolListSection.
+; RUN: llvm-profdata merge -sample -extbinary -md5-prof-sym-list -prof-sym-list=%S/Inputs/profile-symbol-list-1.text %S/Inputs/sample-profile.proftext -o %t.md5.output
+; RUN: llvm-profdata show -sample -show-sec-info-only %t.md5.output | FileCheck %s -check-prefix=MD5
+
+; MD5: ProfileSymbolListSection - Offset: {{.*}}, Size: {{.*}}, Flags: {{{.*}}md5}
+
;; Generate two SampleFDO binary profiles and merge them.
;; Tests that the vtable counters in the merged profile are the aggregated
;; result from both sources.
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index e52bbdb069ff0..4e38cb0a91388 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -476,6 +476,18 @@ TEST_F(SampleProfTest, roundtrip_md5_ext_binary_profile) {
testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, true);
}
+TEST_F(SampleProfTest, roundtrip_eytzinger_ext_binary_profile) {
+ const char *Args[] = {"SampleProfTest", "--md5-prof-sym-list=true"};
+ cl::ResetAllOptionOccurrences();
+ cl::ParseCommandLineOptions(2, Args, StringRef(), &llvm::nulls());
+
+ testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false, false);
+
+ const char *ArgsFalse[] = {"SampleProfTest", "--md5-prof-sym-list=false"};
+ cl::ResetAllOptionOccurrences();
+ cl::ParseCommandLineOptions(2, ArgsFalse, StringRef(), &llvm::nulls());
+}
+
TEST_F(SampleProfTest, remap_text_profile) {
testRoundTrip(SampleProfileFormat::SPF_Text, true, false);
}
@@ -687,4 +699,20 @@ TEST_F(SampleProfTest, SampleProfileFormatVersion105) {
EXPECT_EQ(ReadVersionOrErr.getError(), sampleprof_error::unsupported_version);
}
+TEST_F(SampleProfTest, ProfileSymbolListMD5) {
+ std::vector<uint64_t> Keys = {FunctionId("foo").getHashCode(),
+ FunctionId("bar").getHashCode()};
+ auto Table =
+ llvm::EytzingerTable<support::ulittle64_t>::create(std::move(Keys));
+
+ ProfileSymbolList List;
+ List.setColdGUIDTable(
+ EytzingerTableSpan<support::ulittle64_t>(Table.data(), Table.size()));
+
+ EXPECT_TRUE(List.contains("foo"));
+ EXPECT_TRUE(List.contains("bar"));
+ EXPECT_FALSE(List.contains("baz"));
+ EXPECT_EQ(2u, List.size());
+}
+
} // end anonymous namespace
``````````
</details>
https://github.com/llvm/llvm-project/pull/210235
More information about the llvm-commits
mailing list