//===- bolt/Passes/ContinuityStats.cpp --------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements the continuity stats calculation pass. // //===----------------------------------------------------------------------===// #include "bolt/Passes/ContinuityStats.h" #include "bolt/Core/BinaryBasicBlock.h" #include "bolt/Core/BinaryFunction.h" #include "bolt/Utils/CommandLineOpts.h" #include "llvm/Support/CommandLine.h" #include #include #include #define DEBUG_TYPE "bolt-opts" using namespace llvm; using namespace bolt; namespace opts { extern cl::opt Verbosity; cl::opt NumFunctionsForContinuityCheck( "num-functions-for-continuity-check", cl::desc("number of hottest functions to print aggregated " "CFG discontinuity stats of."), cl::init(1000), cl::ZeroOrMore, cl::Hidden, cl::cat(BoltOptCategory)); } // namespace opts namespace { using FunctionListType = std::vector; using function_iterator = FunctionListType::iterator; template void printDistribution(raw_ostream &OS, std::vector &values, bool Fraction = false) { if (values.empty()) return; // Sort values from largest to smallest and print the MAX, TOP 1%, 5%, 10%, // 20%, 50%, 80%, MIN. If Fraction is true, then values are printed as // fractions instead of integers. std::sort(values.begin(), values.end()); auto printLine = [&](std::string Text, double Percent) { int Rank = int(values.size() * (1.0 - Percent / 100)); if (Percent == 0) Rank = values.size() - 1; if (Fraction) OS << " " << Text << std::string(9 - Text.length(), ' ') << ": " << format("%.2lf%%", values[Rank] * 100) << "\n"; else OS << " " << Text << std::string(9 - Text.length(), ' ') << ": " << values[Rank] << "\n"; }; printLine("MAX", 0); const int percentages[] = {1, 5, 10, 20, 50, 80}; for (size_t i = 0; i < sizeof(percentages) / sizeof(percentages[0]); ++i) { printLine("TOP " + std::to_string(percentages[i]) + "%", percentages[i]); } printLine("MIN", 100); } void printCFGContinuityStats(raw_ostream &OS, iterator_range &Functions) { // Given a perfect profile, every positive-execution-count BB should be // connected to an entry of the function through a positive-execution-count // directed path in the control flow graph. std::vector NumUnreachables; std::vector SumECUnreachables; std::vector FractionECUnreachables; for (auto it = Functions.begin(); it != Functions.end(); ++it) { const BinaryFunction *Function = *it; if (Function->size() <= 1) continue; // Compute the sum of all BB execution counts (ECs). size_t NumPosECBBs = 0; size_t SumAllBBEC = 0; for (const BinaryBasicBlock &BB : *Function) { const size_t BBEC = BB.getKnownExecutionCount(); NumPosECBBs += BBEC > 0 ? 1 : 0; SumAllBBEC += BBEC; } // Perform BFS on subgraph of CFG induced by positive weight edges. // Compute the number of BBs reachable from the entry(s) of the function and // the sum of their execution counts (ECs). std::unordered_map IndexToBB; std::unordered_set Visited; std::queue Queue; for (const BinaryBasicBlock &BB : *Function) { // Make sure BB.getIndex() is not already in IndexToBB. assert(IndexToBB.find(BB.getIndex()) == IndexToBB.end()); IndexToBB[BB.getIndex()] = &BB; if (BB.isEntryPoint() && BB.getKnownExecutionCount() > 0) { Queue.push(BB.getIndex()); Visited.insert(BB.getIndex()); } } while (!Queue.empty()) { const unsigned BBIndex = Queue.front(); const BinaryBasicBlock *BB = IndexToBB[BBIndex]; Queue.pop(); auto SuccBIIter = BB->branch_info_begin(); for (const BinaryBasicBlock *Succ : BB->successors()) { const uint64_t Count = SuccBIIter->Count; if (Count == BinaryBasicBlock::COUNT_NO_PROFILE || Count == 0) { ++SuccBIIter; continue; } if (!Visited.insert(Succ->getIndex()).second) { ++SuccBIIter; continue; } Queue.push(Succ->getIndex()); ++SuccBIIter; } } const size_t NumReachableBBs = Visited.size(); // Loop through Visited, and sum the corresponding BBs' execution counts // (ECs). size_t SumReachableBBEC = 0; for (const unsigned BBIndex : Visited) { const BinaryBasicBlock *BB = IndexToBB[BBIndex]; SumReachableBBEC += BB->getKnownExecutionCount(); } const size_t NumPosECBBsUnreachableFromEntry = NumPosECBBs - NumReachableBBs; const size_t SumUnreachableBBEC = SumAllBBEC - SumReachableBBEC; const double FractionECUnreachable = (double)SumUnreachableBBEC / SumAllBBEC; if (opts::Verbosity >= 2 && FractionECUnreachable >= 0.05) { OS << "Non-trivial CFG discontinuity observed in function " << Function->getPrintName() << "\n"; LLVM_DEBUG(Function->dump()); } NumUnreachables.push_back(NumPosECBBsUnreachableFromEntry); SumECUnreachables.push_back(SumUnreachableBBEC); FractionECUnreachables.push_back(FractionECUnreachable); } if (FractionECUnreachables.empty()) return; std::sort(FractionECUnreachables.begin(), FractionECUnreachables.end()); const int Rank = int(FractionECUnreachables.size() * 0.95); OS << format("top 5%% function CFG discontinuity is %.2lf%%\n", FractionECUnreachables[Rank] * 100); if (opts::Verbosity >= 1) { OS << "abbreviations: EC = execution count, POS BBs = positive EC BBs\n" << "distribution of NUM(unreachable POS BBs) among all focal " "functions\n"; printDistribution(OS, NumUnreachables); OS << "distribution of SUM_EC(unreachable POS BBs) among all focal " "functions\n"; printDistribution(OS, SumECUnreachables); OS << "distribution of [(SUM_EC(unreachable POS BBs) / SUM_EC(all " "POS BBs))] among all focal functions\n"; printDistribution(OS, FractionECUnreachables, /*Fraction=*/true); } } void printAll(BinaryContext &BC, FunctionListType &ValidFunctions, size_t NumTopFunctions) { // Sort the list of functions by execution counts (reverse). llvm::sort(ValidFunctions, [&](const BinaryFunction *A, const BinaryFunction *B) { return A->getKnownExecutionCount() > B->getKnownExecutionCount(); }); const size_t RealNumTopFunctions = std::min(NumTopFunctions, ValidFunctions.size()); iterator_range Functions( ValidFunctions.begin(), ValidFunctions.begin() + RealNumTopFunctions); BC.outs() << format("BOLT-INFO: among the hottest %zu functions ", RealNumTopFunctions); printCFGContinuityStats(BC.outs(), Functions); // Print more detailed bucketed stats if requested. if (opts::Verbosity >= 1 && RealNumTopFunctions >= 5) { const size_t PerBucketSize = RealNumTopFunctions / 5; BC.outs() << format( "Detailed stats for 5 buckets, each with %zu functions:\n", PerBucketSize); // For each bucket, print the CFG continuity stats of the functions in the // bucket. for (size_t BucketIndex = 0; BucketIndex < 5; ++BucketIndex) { const size_t StartIndex = BucketIndex * PerBucketSize; const size_t EndIndex = StartIndex + PerBucketSize; iterator_range Functions( ValidFunctions.begin() + StartIndex, ValidFunctions.begin() + EndIndex); const size_t MaxFunctionExecutionCount = ValidFunctions[StartIndex]->getKnownExecutionCount(); const size_t MinFunctionExecutionCount = ValidFunctions[EndIndex - 1]->getKnownExecutionCount(); BC.outs() << format("----------------\n| Bucket %zu: " "|\n----------------\n", BucketIndex + 1) << format( "execution counts of the %zu functions in the bucket: " "%zu-%zu\n", EndIndex - StartIndex, MinFunctionExecutionCount, MaxFunctionExecutionCount); printCFGContinuityStats(BC.outs(), Functions); } } } } // namespace bool PrintContinuityStats::shouldOptimize(const BinaryFunction &BF) const { if (BF.empty() || !BF.hasValidProfile()) return false; return BinaryFunctionPass::shouldOptimize(BF); } Error PrintContinuityStats::runOnFunctions(BinaryContext &BC) { // Create a list of functions with valid profiles. FunctionListType ValidFunctions; for (const auto &BFI : BC.getBinaryFunctions()) { const BinaryFunction *Function = &BFI.second; if (PrintContinuityStats::shouldOptimize(*Function)) ValidFunctions.push_back(Function); } if (ValidFunctions.empty() || opts::NumFunctionsForContinuityCheck == 0) return Error::success(); printAll(BC, ValidFunctions, opts::NumFunctionsForContinuityCheck); return Error::success(); }