diff options
author | William Huang <williamjhuang@google.com> | 2023-05-25 03:35:50 +0000 |
---|---|---|
committer | William Huang <williamjhuang@google.com> | 2023-06-23 21:48:52 +0000 |
commit | 31af18bccea95fe1ae8aa2c51cf7c8e92a1c208e (patch) | |
tree | 843b3a6c1331a405c14beb7118ee9aa6f879d7a4 /llvm/lib/ProfileData/SampleProf.cpp | |
parent | e809ebeb6c8b4986cc82542cffd07a453bfac66c (diff) | |
download | llvm-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/SampleProf.cpp')
-rw-r--r-- | llvm/lib/ProfileData/SampleProf.cpp | 74 |
1 files changed, 20 insertions, 54 deletions
diff --git a/llvm/lib/ProfileData/SampleProf.cpp b/llvm/lib/ProfileData/SampleProf.cpp index fdae8a0..3e22301 100644 --- a/llvm/lib/ProfileData/SampleProf.cpp +++ b/llvm/lib/ProfileData/SampleProf.cpp @@ -202,13 +202,12 @@ void sampleprof::sortFuncProfiles( const SampleProfileMap &ProfileMap, std::vector<NameFunctionSamples> &SortedProfiles) { for (const auto &I : ProfileMap) { - assert(I.first == I.second.getContext() && "Inconsistent profile map"); - SortedProfiles.push_back(std::make_pair(I.second.getContext(), &I.second)); + SortedProfiles.emplace_back(I.first, &I.second); } llvm::stable_sort(SortedProfiles, [](const NameFunctionSamples &A, const NameFunctionSamples &B) { if (A.second->getTotalSamples() == B.second->getTotalSamples()) - return A.first < B.first; + return A.second->getContext() < B.second->getContext(); return A.second->getTotalSamples() > B.second->getTotalSamples(); }); } @@ -357,13 +356,13 @@ void SampleContextTrimmer::trimAndMergeColdContextProfiles( // Filter the cold profiles from ProfileMap and move them into a tmp // container - std::vector<std::pair<SampleContext, const FunctionSamples *>> ColdProfiles; + std::vector<std::pair<hash_code, const FunctionSamples *>> ColdProfiles; for (const auto &I : ProfileMap) { - const SampleContext &Context = I.first; + const SampleContext &Context = I.second.getContext(); const FunctionSamples &FunctionProfile = I.second; if (FunctionProfile.getTotalSamples() < ColdCountThreshold && (!TrimBaseProfileOnly || Context.isBaseContext())) - ColdProfiles.emplace_back(Context, &I.second); + ColdProfiles.emplace_back(I.first, &I.second); } // Remove the cold profile from ProfileMap and merge them into @@ -374,8 +373,8 @@ void SampleContextTrimmer::trimAndMergeColdContextProfiles( auto MergedContext = I.second->getContext().getContextFrames(); if (ColdContextFrameLength < MergedContext.size()) MergedContext = MergedContext.take_back(ColdContextFrameLength); - auto Ret = MergedProfileMap.emplace(MergedContext, FunctionSamples()); - FunctionSamples &MergedProfile = Ret.first->second; + // Need to set MergedProfile's context here otherwise it will be lost. + FunctionSamples &MergedProfile = MergedProfileMap.Create(MergedContext); MergedProfile.merge(*I.second); } ProfileMap.erase(I.first); @@ -385,57 +384,17 @@ void SampleContextTrimmer::trimAndMergeColdContextProfiles( for (const auto &I : MergedProfileMap) { // Filter the cold merged profile if (TrimColdContext && I.second.getTotalSamples() < ColdCountThreshold && - ProfileMap.find(I.first) == ProfileMap.end()) + ProfileMap.find(I.second.getContext()) == ProfileMap.end()) continue; // Merge the profile if the original profile exists, otherwise just insert - // as a new profile - auto Ret = ProfileMap.emplace(I.first, FunctionSamples()); - if (Ret.second) { - SampleContext FContext(Ret.first->first, RawContext); - FunctionSamples &FProfile = Ret.first->second; - FProfile.setContext(FContext); - } + // as a new profile. If inserted as a new profile from MergedProfileMap, it + // already has the right context. + auto Ret = ProfileMap.emplace(I.second.getContext(), FunctionSamples()); FunctionSamples &OrigProfile = Ret.first->second; OrigProfile.merge(I.second); } } -void SampleContextTrimmer::canonicalizeContextProfiles() { - std::vector<SampleContext> ProfilesToBeRemoved; - SampleProfileMap ProfilesToBeAdded; - for (auto &I : ProfileMap) { - FunctionSamples &FProfile = I.second; - SampleContext &Context = FProfile.getContext(); - if (I.first == Context) - continue; - - // Use the context string from FunctionSamples to update the keys of - // ProfileMap. They can get out of sync after context profile promotion - // through pre-inliner. - // Duplicate the function profile for later insertion to avoid a conflict - // caused by a context both to be add and to be removed. This could happen - // when a context is promoted to another context which is also promoted to - // the third context. For example, given an original context A @ B @ C that - // is promoted to B @ C and the original context B @ C which is promoted to - // just C, adding B @ C to the profile map while removing same context (but - // with different profiles) from the map can cause a conflict if they are - // not handled in a right order. This can be solved by just caching the - // profiles to be added. - auto Ret = ProfilesToBeAdded.emplace(Context, FProfile); - (void)Ret; - assert(Ret.second && "Context conflict during canonicalization"); - ProfilesToBeRemoved.push_back(I.first); - } - - for (auto &I : ProfilesToBeRemoved) { - ProfileMap.erase(I); - } - - for (auto &I : ProfilesToBeAdded) { - ProfileMap.emplace(I.first, I.second); - } -} - std::error_code ProfileSymbolList::write(raw_ostream &OS) { // Sort the symbols before output. If doing compression. // It will make the compression much more effective. @@ -509,6 +468,7 @@ void ProfileConverter::convertCSProfiles(ProfileConverter::FrameNode &Node) { if (!ChildProfile) continue; SampleContext OrigChildContext = ChildProfile->getContext(); + hash_code OrigChildContextHash = OrigChildContext.getHashCode(); // Reset the child context to be contextless. ChildProfile->getContext().setName(OrigChildContext.getName()); if (NodeProfile) { @@ -524,6 +484,7 @@ void ProfileConverter::convertCSProfiles(ProfileConverter::FrameNode &Node) { NodeProfile->removeTotalSamples(Count); } + hash_code NewChildProfileHash(0); // Separate child profile to be a standalone profile, if the current parent // profile doesn't exist. This is a duplicating operation when the child // profile is already incorporated into the parent which is still useful and @@ -532,15 +493,20 @@ void ProfileConverter::convertCSProfiles(ProfileConverter::FrameNode &Node) { // profile in the prelink phase for to-be-fully-inlined functions. if (!NodeProfile) { ProfileMap[ChildProfile->getContext()].merge(*ChildProfile); + NewChildProfileHash = ChildProfile->getContext().getHashCode(); } else if (GenerateMergedBaseProfiles) { ProfileMap[ChildProfile->getContext()].merge(*ChildProfile); + NewChildProfileHash = ChildProfile->getContext().getHashCode(); auto &SamplesMap = NodeProfile->functionSamplesAt(ChildNode.CallSiteLoc); SamplesMap[ChildProfile->getName().str()].getContext().setAttribute( ContextDuplicatedIntoBase); } - // Remove the original child profile. - ProfileMap.erase(OrigChildContext); + // Remove the original child profile. Check if MD5 of new child profile + // collides with old profile, in this case the [] operator already + // overwritten it without the need of erase. + if (NewChildProfileHash != OrigChildContextHash) + ProfileMap.erase(OrigChildContextHash); } } |