aboutsummaryrefslogtreecommitdiff
path: root/llvm/lib/ProfileData/SampleProfReader.cpp
diff options
context:
space:
mode:
authorWilliam Huang <williamjhuang@google.com>2023-05-25 03:35:50 +0000
committerWilliam Huang <williamjhuang@google.com>2023-06-23 21:48:52 +0000
commit31af18bccea95fe1ae8aa2c51cf7c8e92a1c208e (patch)
tree843b3a6c1331a405c14beb7118ee9aa6f879d7a4 /llvm/lib/ProfileData/SampleProfReader.cpp
parente809ebeb6c8b4986cc82542cffd07a453bfac66c (diff)
downloadllvm-31af18bccea95fe1ae8aa2c51cf7c8e92a1c208e.zip
llvm-31af18bccea95fe1ae8aa2c51cf7c8e92a1c208e.tar.gz
llvm-31af18bccea95fe1ae8aa2c51cf7c8e92a1c208e.tar.bz2
[llvm-profdata] Refactoring Sample Profile Reader to increase FDO build speed using MD5 as key to Sample Profile map
This is phase 1 of multiple planned improvements on the sample profile loader. The major change is to use MD5 hash code ((instead of the function itself) as the key to look up the function offset table and the profiles, which significantly reduce the time it takes to construct the map. The optimization is based on the fact that many practical sample profiles are using MD5 values for function names to reduce profile size, so we shouldn't need to convert the MD5 to a string and then to a SampleContext and use it as the map's key, because it's extremely slow. Several changes to note: (1) For non-CS SampleContext, if it is already MD5 string, the hash value will be its integral value, instead of hashing the MD5 again. In phase 2 this is going to be optimized further using a union to represent MD5 function (without converting it to string) and regular function names. (2) The SampleProfileMap is a wrapper to *map<uint64_t, FunctionSamples>, while providing interface allowing using SampleContext as key, so that existing code still work. It will check for MD5 collision (unlikely but not too unlikely, since we only takes the lower 64 bits) and handle it to at least guarantee compilation correctness (conflicting old profile is dropped, instead of returning an old profile with inconsistent context). Other code should not try to use MD5 as key to access the map directly, because it will not be able to handle MD5 collision at all. (see exception at (5) ) (3) Any SampleProfileMap::emplace() followed by SampleContext assignment if newly inserted, should be replaced with SampleProfileMap::Create(), which does the same thing. (4) Previously we ensure an invariant that in SampleProfileMap, the key is equal to the Context of the value, for profile map that is eventually being used for output (as in llvm-profdata/llvm-profgen). Since the key became MD5 hash, only the value keeps the context now, in several places where an intermediate SampleProfileMap is created, each new FunctionSample's context is set immediately after insertion, which is necessary to "remember" the context otherwise irretrievable. (5) When reading a profile, we cache the MD5 values of all functions, because they are used at least twice (one to index into FuncOffsetTable, the other into SampleProfileMap, more if there are additional sections), in this case the SampleProfileMap is directly accessed with MD5 value so that we don't recalculate it each time (expensive) Performance impact: When reading a ~1GB extbinary profile (fixed length MD5, not compressed) with 10 million function names and 2.5 million top level functions (non CS functions, each function has varying nesting level from 0 to 20), this patch improves the function offset table loading time by 20%, and improves full profile read by 5%. Reviewed By: davidxl, snehasish Differential Revision: https://reviews.llvm.org/D147740
Diffstat (limited to 'llvm/lib/ProfileData/SampleProfReader.cpp')
-rw-r--r--llvm/lib/ProfileData/SampleProfReader.cpp121
1 files changed, 88 insertions, 33 deletions
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index fbdd9a3..d71fe11 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -61,9 +61,9 @@ static cl::opt<bool> ProfileIsFSDisciminator(
///
/// \param FContext Name + context of the function to print.
/// \param OS Stream to emit the output to.
-void SampleProfileReader::dumpFunctionProfile(SampleContext FContext,
+void SampleProfileReader::dumpFunctionProfile(const FunctionSamples &FS,
raw_ostream &OS) {
- OS << "Function: " << FContext.toString() << ": " << Profiles[FContext];
+ OS << "Function: " << FS.getContext().toString() << ": " << FS;
}
/// Dump all the function profiles found on stream \p OS.
@@ -71,7 +71,7 @@ void SampleProfileReader::dump(raw_ostream &OS) {
std::vector<NameFunctionSamples> V;
sortFuncProfiles(Profiles, V);
for (const auto &I : V)
- dumpFunctionProfile(I.first, OS);
+ dumpFunctionProfile(*I.second, OS);
}
static void dumpFunctionProfileJson(const FunctionSamples &S,
@@ -355,9 +355,7 @@ std::error_code SampleProfileReaderText::readImpl() {
SampleContext FContext(FName, CSNameTable);
if (FContext.hasContext())
++CSProfileCount;
- Profiles[FContext] = FunctionSamples();
- FunctionSamples &FProfile = Profiles[FContext];
- FProfile.setContext(FContext);
+ FunctionSamples &FProfile = Profiles.Create(FContext);
MergeResult(Result, FProfile.addTotalSamples(NumSamples));
MergeResult(Result, FProfile.addHeadSamples(NumHeadSamples));
InlineStack.clear();
@@ -525,7 +523,8 @@ inline ErrorOr<size_t> SampleProfileReaderBinary::readStringIndex(T &Table) {
return *Idx;
}
-ErrorOr<StringRef> SampleProfileReaderBinary::readStringFromTable() {
+ErrorOr<StringRef>
+SampleProfileReaderBinary::readStringFromTable(size_t *RetIdx) {
auto Idx = readStringIndex(NameTable);
if (std::error_code EC = Idx.getError())
return EC;
@@ -543,30 +542,48 @@ ErrorOr<StringRef> SampleProfileReaderBinary::readStringFromTable() {
MD5NameMemStart + (*Idx) * sizeof(uint64_t));
SR = MD5StringBuf.emplace_back(std::to_string(FID));
}
+ if (RetIdx)
+ *RetIdx = *Idx;
return SR;
}
-ErrorOr<SampleContextFrames> SampleProfileReaderBinary::readContextFromTable() {
+ErrorOr<SampleContextFrames>
+SampleProfileReaderBinary::readContextFromTable(size_t *RetIdx) {
auto ContextIdx = readNumber<size_t>();
if (std::error_code EC = ContextIdx.getError())
return EC;
if (*ContextIdx >= CSNameTable.size())
return sampleprof_error::truncated_name_table;
+ if (RetIdx)
+ *RetIdx = *ContextIdx;
return CSNameTable[*ContextIdx];
}
-ErrorOr<SampleContext> SampleProfileReaderBinary::readSampleContextFromTable() {
+ErrorOr<std::pair<SampleContext, hash_code>>
+SampleProfileReaderBinary::readSampleContextFromTable() {
+ SampleContext Context;
+ size_t Idx;
if (ProfileIsCS) {
- auto FContext(readContextFromTable());
+ auto FContext(readContextFromTable(&Idx));
if (std::error_code EC = FContext.getError())
return EC;
- return SampleContext(*FContext);
+ Context = SampleContext(*FContext);
} else {
- auto FName(readStringFromTable());
+ auto FName(readStringFromTable(&Idx));
if (std::error_code EC = FName.getError())
return EC;
- return SampleContext(*FName);
+ Context = SampleContext(*FName);
+ }
+ hash_code Hash = MD5SampleContextStart[Idx];
+ // Lazy computing of hash value, write back to the table to cache it. Only
+ // compute the context's hash value if it is being referenced for the first
+ // time.
+ if (Hash == hash_code(0)) {
+ assert(MD5SampleContextStart == MD5SampleContextTable.data());
+ Hash = Context.getHashCode();
+ MD5SampleContextTable[Idx] = Hash;
}
+ return std::make_pair(Context, Hash);
}
std::error_code
@@ -659,16 +676,18 @@ SampleProfileReaderBinary::readFuncProfile(const uint8_t *Start) {
if (std::error_code EC = NumHeadSamples.getError())
return EC;
- ErrorOr<SampleContext> FContext(readSampleContextFromTable());
- if (std::error_code EC = FContext.getError())
+ auto FContextHash(readSampleContextFromTable());
+ if (std::error_code EC = FContextHash.getError())
return EC;
- Profiles[*FContext] = FunctionSamples();
- FunctionSamples &FProfile = Profiles[*FContext];
- FProfile.setContext(*FContext);
+ auto &[FContext, Hash] = *FContextHash;
+ // Use the cached hash value for insertion instead of recalculating it.
+ auto Res = Profiles.try_emplace(Hash, FContext, FunctionSamples());
+ FunctionSamples &FProfile = Res.first->second;
+ FProfile.setContext(FContext);
FProfile.addHeadSamples(*NumHeadSamples);
- if (FContext->hasContext())
+ if (FContext.hasContext())
CSProfileCount++;
if (std::error_code EC = readProfile(FProfile))
@@ -816,18 +835,21 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncOffsetTable() {
FuncOffsetTable.reserve(*Size);
for (uint64_t I = 0; I < *Size; ++I) {
- auto FContext(readSampleContextFromTable());
- if (std::error_code EC = FContext.getError())
+ auto FContextHash(readSampleContextFromTable());
+ if (std::error_code EC = FContextHash.getError())
return EC;
+ auto &[FContext, Hash] = *FContextHash;
auto Offset = readNumber<uint64_t>();
if (std::error_code EC = Offset.getError())
return EC;
if (UseFuncOffsetList)
- FuncOffsetList.emplace_back(*FContext, *Offset);
+ FuncOffsetList.emplace_back(FContext, *Offset);
else
- FuncOffsetTable[*FContext] = *Offset;
+ // Because Porfiles replace existing value with new value if collision
+ // happens, we also use the latest offset so that they are consistent.
+ FuncOffsetTable[Hash] = *Offset;
}
return sampleprof_error::success;
@@ -900,8 +922,8 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles() {
} else if (useMD5()) {
assert(!useFuncOffsetList());
for (auto Name : FuncsToUse) {
- auto GUID = std::to_string(MD5Hash(Name));
- auto iter = FuncOffsetTable.find(StringRef(GUID));
+ auto GUID = MD5Hash(Name);
+ auto iter = FuncOffsetTable.find(GUID);
if (iter == FuncOffsetTable.end())
continue;
const uint8_t *FuncProfileAddr = Start + iter->second;
@@ -922,7 +944,7 @@ std::error_code SampleProfileReaderExtBinaryBase::readFuncProfiles() {
} else {
assert(!useFuncOffsetList());
for (auto Name : FuncsToUse) {
- auto iter = FuncOffsetTable.find(Name);
+ auto iter = FuncOffsetTable.find(MD5Hash(Name));
if (iter == FuncOffsetTable.end())
continue;
const uint8_t *FuncProfileAddr = Start + iter->second;
@@ -1050,17 +1072,30 @@ std::error_code SampleProfileReaderBinary::readNameTable() {
NameTable.clear();
NameTable.reserve(*Size);
+ if (!ProfileIsCS) {
+ MD5SampleContextTable.clear();
+ if (UseMD5)
+ MD5SampleContextTable.reserve(*Size);
+ else
+ // If we are using strings, delay MD5 computation since only a portion of
+ // names are used by top level functions. Use 0 to indicate MD5 value is
+ // to be calculated as no known string has a MD5 value of 0.
+ MD5SampleContextTable.resize(*Size);
+ }
for (size_t I = 0; I < *Size; ++I) {
auto Name(readString());
if (std::error_code EC = Name.getError())
return EC;
if (UseMD5) {
uint64_t FID = MD5Hash(*Name);
+ if (!ProfileIsCS)
+ MD5SampleContextTable.emplace_back(FID);
NameTable.emplace_back(MD5StringBuf.emplace_back(std::to_string(FID)));
} else
NameTable.push_back(*Name);
}
-
+ if (!ProfileIsCS)
+ MD5SampleContextStart = MD5SampleContextTable.data();
return sampleprof_error::success;
}
@@ -1088,6 +1123,8 @@ SampleProfileReaderExtBinaryBase::readNameTableSec(bool IsMD5,
NameTable.clear();
NameTable.resize(*Size);
MD5NameMemStart = Data;
+ if (!ProfileIsCS)
+ MD5SampleContextStart = reinterpret_cast<const hash_code *>(Data);
Data = Data + (*Size) * sizeof(uint64_t);
return sampleprof_error::success;
}
@@ -1101,12 +1138,20 @@ SampleProfileReaderExtBinaryBase::readNameTableSec(bool IsMD5,
MD5StringBuf.reserve(MD5StringBuf.size() + *Size);
NameTable.clear();
NameTable.reserve(*Size);
+ if (!ProfileIsCS) {
+ MD5SampleContextTable.clear();
+ MD5SampleContextTable.reserve(*Size);
+ }
for (size_t I = 0; I < *Size; ++I) {
auto FID = readNumber<uint64_t>();
if (std::error_code EC = FID.getError())
return EC;
+ if (!ProfileIsCS)
+ MD5SampleContextTable.emplace_back(*FID);
NameTable.emplace_back(MD5StringBuf.emplace_back(std::to_string(*FID)));
}
+ if (!ProfileIsCS)
+ MD5SampleContextStart = MD5SampleContextTable.data();
return sampleprof_error::success;
}
@@ -1124,6 +1169,14 @@ std::error_code SampleProfileReaderExtBinaryBase::readCSNameTableSec() {
CSNameTable.clear();
CSNameTable.reserve(*Size);
+ if (ProfileIsCS) {
+ // Delay MD5 computation of CS context until they are needed. Use 0 to
+ // indicate MD5 value is to be calculated as no known string has a MD5
+ // value of 0.
+ MD5SampleContextTable.clear();
+ MD5SampleContextTable.resize(*Size);
+ MD5SampleContextStart = MD5SampleContextTable.data();
+ }
for (size_t I = 0; I < *Size; ++I) {
CSNameTable.emplace_back(SampleContextFrameVector());
auto ContextSize = readNumber<uint32_t>();
@@ -1187,16 +1240,17 @@ SampleProfileReaderExtBinaryBase::readFuncMetadata(bool ProfileHasAttribute,
if (std::error_code EC = Discriminator.getError())
return EC;
- auto FContext(readSampleContextFromTable());
- if (std::error_code EC = FContext.getError())
+ auto FContextHash(readSampleContextFromTable());
+ if (std::error_code EC = FContextHash.getError())
return EC;
+ auto &[FContext, Hash] = *FContextHash;
FunctionSamples *CalleeProfile = nullptr;
if (FProfile) {
CalleeProfile = const_cast<FunctionSamples *>(
&FProfile->functionSamplesAt(LineLocation(
*LineOffset,
- *Discriminator))[std::string(FContext.get().getName())]);
+ *Discriminator))[std::string(FContext.getName())]);
}
if (std::error_code EC =
readFuncMetadata(ProfileHasAttribute, CalleeProfile))
@@ -1211,11 +1265,12 @@ SampleProfileReaderExtBinaryBase::readFuncMetadata(bool ProfileHasAttribute,
std::error_code
SampleProfileReaderExtBinaryBase::readFuncMetadata(bool ProfileHasAttribute) {
while (Data < End) {
- auto FContext(readSampleContextFromTable());
- if (std::error_code EC = FContext.getError())
+ auto FContextHash(readSampleContextFromTable());
+ if (std::error_code EC = FContextHash.getError())
return EC;
+ auto &[FContext, Hash] = *FContextHash;
FunctionSamples *FProfile = nullptr;
- auto It = Profiles.find(*FContext);
+ auto It = Profiles.find(FContext);
if (It != Profiles.end())
FProfile = &It->second;