diff --git a/llvm/include/llvm/ProfileData/SampleProf.h b/llvm/include/llvm/ProfileData/SampleProf.h --- a/llvm/include/llvm/ProfileData/SampleProf.h +++ b/llvm/include/llvm/ProfileData/SampleProf.h @@ -498,6 +498,21 @@ return CallsiteSamples; } + /// Return the maximum of sample counts in a function body including functions + /// inlined in it. + uint64_t getMaxCountInside() const { + uint64_t MaxCount = 0; + for (const auto &L : getBodySamples()) { + MaxCount = std::max(MaxCount, L.second.getSamples()); + } + for (const auto &C : getCallsiteSamples()) { + for (const auto &F : C.second) { + MaxCount = std::max(MaxCount, F.second.getMaxCountInside()); + } + } + return MaxCount; + } + /// Merge the samples in \p Other into this one. /// Optionally scale samples by \p Weight. sampleprof_error merge(const FunctionSamples &Other, uint64_t Weight = 1) { diff --git a/llvm/test/tools/llvm-profdata/Inputs/sample-hot-func-list.proftext b/llvm/test/tools/llvm-profdata/Inputs/sample-hot-func-list.proftext new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-profdata/Inputs/sample-hot-func-list.proftext @@ -0,0 +1,41 @@ +_Z3bari:20301:1437 + 1: 1437 +_Z3fooi:7711:610 + 1: 610 +main:184019:0 + 4: 534 + 4.2: 534 + 5: 1075 + 5.1: 1075 + 6: 2080 + 7: 534 + 9: 2300 _Z3bari:1471 _Z3fooi:631 + 10: inline1:1000 + 1: 1000 + 10: inline2:2000 + 1: 2000 +_Z3bazi:20305:1000 + 1: 1000 +Func1:1523:169 + 1: 169 + 7: 563 +Func2:17043:1594 + 1: 1594 + 3: 1594 + 6: 2009 Func1:150 Func3:1789 + 13: 3105 + 17: 3105 + 19: 1594 +Func3:97401:3035 + 1: 3035 + 5: 7344 + 9: 10640 + 11: 10640 + 15: 3035 +Func4:465:210 + 1: 210 +Func5:6948:470 + 1: 470 + 3: 3507 +Func6:310:102 + 1: 102 diff --git a/llvm/test/tools/llvm-profdata/sample-hot-func-list.test b/llvm/test/tools/llvm-profdata/sample-hot-func-list.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-profdata/sample-hot-func-list.test @@ -0,0 +1,12 @@ +; RUN: llvm-profdata show --sample --hot-func-list %S/Inputs/sample-hot-func-list.proftext | FileCheck %s +; CHECK: 8 out of 10 functions with profile (80.00%) are considered hot functions (max sample >= 470). +; CHECK-NEXT: 355251 out of 356026 profile counts (99.78%) are from hot functions. +; CHECK-NEXT: Total sample (%) Max sample Entry sample Function name +; CHECK-NEXT: 184019 (51.69%) 2300 534 main +; CHECK-NEXT: 97401 (27.36%) 10640 3035 Func3 +; CHECK-NEXT: 20305 (5.70%) 1000 1000 _Z3bazi +; CHECK-NEXT: 20301 (5.70%) 1437 1437 _Z3bari +; CHECK-NEXT: 17043 (4.79%) 3105 1594 Func2 +; CHECK-NEXT: 7711 (2.17%) 610 610 _Z3fooi +; CHECK-NEXT: 6948 (1.95%) 3507 470 Func5 +; CHECK-NEXT: 1523 (0.43%) 563 169 Func1 diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp --- a/llvm/tools/llvm-profdata/llvm-profdata.cpp +++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp @@ -23,11 +23,12 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" +#include "llvm/Support/FormattedStream.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" -#include "llvm/Support/Threading.h" #include "llvm/Support/ThreadPool.h" +#include "llvm/Support/Threading.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include @@ -1026,11 +1027,142 @@ } } +struct HotFuncInfo { + StringRef FuncName; + uint64_t TotalCount; + double TotalCountPercent; + uint64_t MaxCount; + uint64_t EntryCount; + + HotFuncInfo() + : FuncName(), TotalCount(0), TotalCountPercent(0.0f), MaxCount(0), + EntryCount(0) {} + + HotFuncInfo(StringRef FN, uint64_t TS, double TSP, uint64_t MS, uint64_t ES) + : FuncName(FN), TotalCount(TS), TotalCountPercent(TSP), MaxCount(MS), + EntryCount(ES) {} +}; + +// Print out detailed information about hot functions in PrintValues vector. +// Users specify titles and offset of every columns through ColumnTitle and +// ColumnOffset. The size of ColumnTitle and ColumnOffset need to be the same +// and at least 4. Besides, users can optionally give a HotFuncMetric string to +// print out or let it be an empty string. +static void dumpHotFunctionList(const std::vector &ColumnTitle, + const std::vector &ColumnOffset, + const std::vector &PrintValues, + uint64_t HotFuncCount, uint64_t TotalFuncCount, + uint64_t HotProfCount, uint64_t TotalProfCount, + const std::string &HotFuncMetric, + raw_fd_ostream &OS) { + assert(ColumnOffset.size() == ColumnTitle.size()); + assert(ColumnTitle.size() >= 4); + assert(TotalFuncCount > 0); + double TotalProfPercent = 0; + if (TotalProfCount > 0) + TotalProfPercent = ((double)HotProfCount) / TotalProfCount * 100; + + formatted_raw_ostream FOS(OS); + FOS << HotFuncCount << " out of " << TotalFuncCount + << " functions with profile (" + << format("%.2f%%", (((double)HotFuncCount) / TotalFuncCount * 100)) + << ") are considered hot functions"; + if (!HotFuncMetric.empty()) + FOS << " (" << HotFuncMetric << ")"; + FOS << ".\n"; + FOS << HotProfCount << " out of " << TotalProfCount << " profile counts (" + << format("%.2f%%", TotalProfPercent) << ") are from hot functions.\n"; + + for (size_t I = 0; I < ColumnTitle.size(); ++I) { + FOS.PadToColumn(ColumnOffset[I]); + FOS << ColumnTitle[I]; + } + FOS << "\n"; + + for (const auto &R : PrintValues) { + FOS.PadToColumn(ColumnOffset[0]); + FOS << R.TotalCount << " (" << format("%.2f%%", R.TotalCountPercent) << ")"; + FOS.PadToColumn(ColumnOffset[1]); + FOS << R.MaxCount; + FOS.PadToColumn(ColumnOffset[2]); + FOS << R.EntryCount; + FOS.PadToColumn(ColumnOffset[3]); + FOS << R.FuncName << "\n"; + } + return; +} + +static int +showHotFunctionList(const StringMap &Profiles, + ProfileSummary &PS, raw_fd_ostream &OS) { + using namespace sampleprof; + + const uint32_t HotFuncCutoff = 990000; + auto &SummaryVector = PS.getDetailedSummary(); + uint64_t MinCountThreshold = 0; + for (const auto &SummaryEntry : SummaryVector) { + if (SummaryEntry.Cutoff == HotFuncCutoff) { + MinCountThreshold = SummaryEntry.MinCount; + break; + } + } + assert(MinCountThreshold != 0); + + // Traverse all functions in the profile and keep only hot functions. + // The following loop also calculates the sum of total samples of all + // functions. + std::multimap, + std::greater> + HotFunc; + uint64_t ProfileTotalSample = 0; + uint64_t HotFuncSample = 0; + uint64_t HotFuncCount = 0; + uint64_t MaxCount = 0; + for (const auto &I : Profiles) { + const auto &FuncProf = I.second; + ProfileTotalSample += FuncProf.getTotalSamples(); + MaxCount = FuncProf.getMaxCountInside(); + + // MinCountThreshold is a block/line threshold computed for a given cutoff. + // We intentionally compare the maximum sample count in a function with this + // threshold to get an approximate threshold for hot functions. + if (MaxCount >= MinCountThreshold) { + HotFunc.emplace(FuncProf.getTotalSamples(), + std::make_pair(&(I.second), MaxCount)); + HotFuncSample += FuncProf.getTotalSamples(); + ++HotFuncCount; + } + } + + std::vector ColumnTitle{"Total sample (%)", "Max sample", + "Entry sample", "Function name"}; + std::vector ColumnOffset{0, 24, 42, 58}; + std::string Metric = + std::string("max sample >= ") + std::to_string(MinCountThreshold); + std::vector PrintValues; + for (const auto &FuncPair : HotFunc) { + const auto &FuncPtr = FuncPair.second.first; + double TotalSamplePercent = + (ProfileTotalSample > 0) + ? (FuncPtr->getTotalSamples() * 100.0) / ProfileTotalSample + : 0; + PrintValues.emplace_back(HotFuncInfo( + FuncPtr->getFuncName(), FuncPtr->getTotalSamples(), TotalSamplePercent, + FuncPair.second.second, FuncPtr->getEntrySamples())); + } + dumpHotFunctionList(ColumnTitle, ColumnOffset, PrintValues, HotFuncCount, + Profiles.size(), HotFuncSample, ProfileTotalSample, + Metric, OS); + + return 0; +} + static int showSampleProfile(const std::string &Filename, bool ShowCounts, bool ShowAllFunctions, bool ShowDetailedSummary, const std::string &ShowFunction, bool ShowProfileSymbolList, - bool ShowSectionInfoOnly, raw_fd_ostream &OS) { + bool ShowSectionInfoOnly, bool ShowHotFuncList, + raw_fd_ostream &OS) { using namespace sampleprof; LLVMContext Context; auto ReaderOrErr = SampleProfileReader::create(Filename, Context); @@ -1064,6 +1196,9 @@ PS.printDetailedSummary(OS); } + if (ShowHotFuncList) + showHotFunctionList(Reader->getProfiles(), Reader->getSummary(), OS); + return 0; } @@ -1090,6 +1225,9 @@ cl::desc( "Cutoff percentages (times 10000) for generating detailed summary"), cl::value_desc("800000,901000,999999")); + cl::opt ShowHotFuncList( + "hot-func-list", cl::init(false), + cl::desc("Show profile summary of a list of hot functions")); cl::opt ShowAllFunctions("all-functions", cl::init(false), cl::desc("Details for every function")); cl::opt ShowCS("showcs", cl::init(false), @@ -1153,7 +1291,8 @@ else return showSampleProfile(Filename, ShowCounts, ShowAllFunctions, ShowDetailedSummary, ShowFunction, - ShowProfileSymbolList, ShowSectionInfoOnly, OS); + ShowProfileSymbolList, ShowSectionInfoOnly, + ShowHotFuncList, OS); } int main(int argc, const char *argv[]) {