1 //===- bolt/Passes/CacheMetrics.cpp - Metrics for instruction cache -------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file implements the CacheMetrics class and functions for showing metrics 10 // of cache lines. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "bolt/Passes/CacheMetrics.h" 15 #include "bolt/Core/BinaryBasicBlock.h" 16 #include "bolt/Core/BinaryFunction.h" 17 #include "llvm/Support/CommandLine.h" 18 #include <unordered_map> 19 20 using namespace llvm; 21 using namespace bolt; 22 23 namespace opts { 24 25 extern cl::OptionCategory BoltOptCategory; 26 27 extern cl::opt<double> ForwardWeight; 28 extern cl::opt<double> BackwardWeight; 29 extern cl::opt<unsigned> ForwardDistance; 30 extern cl::opt<unsigned> BackwardDistance; 31 extern cl::opt<unsigned> ITLBPageSize; 32 extern cl::opt<unsigned> ITLBEntries; 33 34 } // namespace opts 35 36 namespace { 37 38 /// Initialize and return a position map for binary basic blocks 39 void extractBasicBlockInfo( 40 const std::vector<BinaryFunction *> &BinaryFunctions, 41 std::unordered_map<BinaryBasicBlock *, uint64_t> &BBAddr, 42 std::unordered_map<BinaryBasicBlock *, uint64_t> &BBSize) { 43 44 for (BinaryFunction *BF : BinaryFunctions) { 45 const BinaryContext &BC = BF->getBinaryContext(); 46 for (BinaryBasicBlock *BB : BF->layout()) { 47 if (BF->isSimple() || BC.HasRelocations) { 48 // Use addresses/sizes as in the output binary 49 BBAddr[BB] = BB->getOutputAddressRange().first; 50 BBSize[BB] = BB->getOutputSize(); 51 } else { 52 // Output ranges should match the input if the body hasn't changed 53 BBAddr[BB] = BB->getInputAddressRange().first + BF->getAddress(); 54 BBSize[BB] = BB->getOriginalSize(); 55 } 56 } 57 } 58 } 59 60 /// Calculate TSP metric, which quantifies the number of fallthrough jumps in 61 /// the ordering of basic blocks 62 double 63 calcTSPScore(const std::vector<BinaryFunction *> &BinaryFunctions, 64 const std::unordered_map<BinaryBasicBlock *, uint64_t> &BBAddr, 65 const std::unordered_map<BinaryBasicBlock *, uint64_t> &BBSize) { 66 67 double Score = 0; 68 for (BinaryFunction *BF : BinaryFunctions) { 69 if (!BF->hasProfile()) 70 continue; 71 for (BinaryBasicBlock *SrcBB : BF->layout()) { 72 auto BI = SrcBB->branch_info_begin(); 73 for (BinaryBasicBlock *DstBB : SrcBB->successors()) { 74 if (SrcBB != DstBB && BI->Count != BinaryBasicBlock::COUNT_NO_PROFILE && 75 BBAddr.at(SrcBB) + BBSize.at(SrcBB) == BBAddr.at(DstBB)) 76 Score += BI->Count; 77 ++BI; 78 } 79 } 80 } 81 return Score; 82 } 83 84 /// Calculate Ext-TSP metric, which quantifies the expected number of i-cache 85 /// misses for a given ordering of basic blocks 86 double calcExtTSPScore( 87 const std::vector<BinaryFunction *> &BinaryFunctions, 88 const std::unordered_map<BinaryBasicBlock *, uint64_t> &BBAddr, 89 const std::unordered_map<BinaryBasicBlock *, uint64_t> &BBSize) { 90 91 double Score = 0.0; 92 for (BinaryFunction *BF : BinaryFunctions) { 93 if (!BF->hasProfile()) 94 continue; 95 for (BinaryBasicBlock *SrcBB : BF->layout()) { 96 auto BI = SrcBB->branch_info_begin(); 97 for (BinaryBasicBlock *DstBB : SrcBB->successors()) { 98 if (DstBB != SrcBB) { 99 Score += CacheMetrics::extTSPScore(BBAddr.at(SrcBB), BBSize.at(SrcBB), 100 BBAddr.at(DstBB), BI->Count); 101 } 102 ++BI; 103 } 104 } 105 } 106 return Score; 107 } 108 109 using Predecessors = std::vector<std::pair<BinaryFunction *, uint64_t>>; 110 111 /// Build a simplified version of the call graph: For every function, keep 112 /// its callers and the frequencies of the calls 113 std::unordered_map<const BinaryFunction *, Predecessors> 114 extractFunctionCalls(const std::vector<BinaryFunction *> &BinaryFunctions) { 115 std::unordered_map<const BinaryFunction *, Predecessors> Calls; 116 117 for (BinaryFunction *SrcFunction : BinaryFunctions) { 118 const BinaryContext &BC = SrcFunction->getBinaryContext(); 119 for (BinaryBasicBlock *BB : SrcFunction->layout()) { 120 // Find call instructions and extract target symbols from each one 121 for (MCInst &Inst : *BB) { 122 if (!BC.MIB->isCall(Inst)) 123 continue; 124 125 // Call info 126 const MCSymbol *DstSym = BC.MIB->getTargetSymbol(Inst); 127 uint64_t Count = BB->getKnownExecutionCount(); 128 // Ignore calls w/o information 129 if (DstSym == nullptr || Count == 0) 130 continue; 131 132 const BinaryFunction *DstFunction = BC.getFunctionForSymbol(DstSym); 133 // Ignore recursive calls 134 if (DstFunction == nullptr || DstFunction->layout_empty() || 135 DstFunction == SrcFunction) 136 continue; 137 138 // Record the call 139 Calls[DstFunction].emplace_back(SrcFunction, Count); 140 } 141 } 142 } 143 return Calls; 144 } 145 146 /// Compute expected hit ratio of the i-TLB cache (optimized by HFSortPlus alg). 147 /// Given an assignment of functions to the i-TLB pages), we divide all 148 /// functions calls into two categories: 149 /// - 'short' ones that have a caller-callee distance less than a page; 150 /// - 'long' ones where the distance exceeds a page. 151 /// The short calls are likely to result in a i-TLB cache hit. For the long 152 /// ones, the hit/miss result depends on the 'hotness' of the page (i.e., how 153 /// often the page is accessed). Assuming that functions are sent to the i-TLB 154 /// cache in a random order, the probability that a page is present in the cache 155 /// is proportional to the number of samples corresponding to the functions on 156 /// the page. The following procedure detects short and long calls, and 157 /// estimates the expected number of cache misses for the long ones. 158 double expectedCacheHitRatio( 159 const std::vector<BinaryFunction *> &BinaryFunctions, 160 const std::unordered_map<BinaryBasicBlock *, uint64_t> &BBAddr, 161 const std::unordered_map<BinaryBasicBlock *, uint64_t> &BBSize) { 162 163 const double PageSize = opts::ITLBPageSize; 164 const uint64_t CacheEntries = opts::ITLBEntries; 165 std::unordered_map<const BinaryFunction *, Predecessors> Calls = 166 extractFunctionCalls(BinaryFunctions); 167 // Compute 'hotness' of the functions 168 double TotalSamples = 0; 169 std::unordered_map<BinaryFunction *, double> FunctionSamples; 170 for (BinaryFunction *BF : BinaryFunctions) { 171 double Samples = 0; 172 for (std::pair<BinaryFunction *, uint64_t> Pair : Calls[BF]) { 173 Samples += Pair.second; 174 } 175 Samples = std::max(Samples, (double)BF->getKnownExecutionCount()); 176 FunctionSamples[BF] = Samples; 177 TotalSamples += Samples; 178 } 179 180 // Compute 'hotness' of the pages 181 std::unordered_map<uint64_t, double> PageSamples; 182 for (BinaryFunction *BF : BinaryFunctions) { 183 if (BF->layout_empty()) 184 continue; 185 double Page = BBAddr.at(BF->layout_front()) / PageSize; 186 PageSamples[Page] += FunctionSamples.at(BF); 187 } 188 189 // Computing the expected number of misses for every function 190 double Misses = 0; 191 for (BinaryFunction *BF : BinaryFunctions) { 192 // Skip the function if it has no samples 193 if (BF->layout_empty() || FunctionSamples.at(BF) == 0.0) 194 continue; 195 double Samples = FunctionSamples.at(BF); 196 double Page = BBAddr.at(BF->layout_front()) / PageSize; 197 // The probability that the page is not present in the cache 198 double MissProb = pow(1.0 - PageSamples[Page] / TotalSamples, CacheEntries); 199 200 // Processing all callers of the function 201 for (std::pair<BinaryFunction *, uint64_t> Pair : Calls[BF]) { 202 BinaryFunction *SrcFunction = Pair.first; 203 double SrcPage = BBAddr.at(SrcFunction->layout_front()) / PageSize; 204 // Is this a 'long' or a 'short' call? 205 if (Page != SrcPage) { 206 // This is a miss 207 Misses += MissProb * Pair.second; 208 } 209 Samples -= Pair.second; 210 } 211 assert(Samples >= 0.0 && "Function samples computed incorrectly"); 212 // The remaining samples likely come from the jitted code 213 Misses += Samples * MissProb; 214 } 215 216 return 100.0 * (1.0 - Misses / TotalSamples); 217 } 218 219 } // namespace 220 221 double CacheMetrics::extTSPScore(uint64_t SrcAddr, uint64_t SrcSize, 222 uint64_t DstAddr, uint64_t Count) { 223 assert(Count != BinaryBasicBlock::COUNT_NO_PROFILE); 224 225 // Fallthrough 226 if (SrcAddr + SrcSize == DstAddr) { 227 // Assume that FallthroughWeight = 1.0 after normalization 228 return static_cast<double>(Count); 229 } 230 // Forward 231 if (SrcAddr + SrcSize < DstAddr) { 232 const uint64_t Dist = DstAddr - (SrcAddr + SrcSize); 233 if (Dist <= opts::ForwardDistance) { 234 double Prob = 1.0 - static_cast<double>(Dist) / opts::ForwardDistance; 235 return opts::ForwardWeight * Prob * Count; 236 } 237 return 0; 238 } 239 // Backward 240 const uint64_t Dist = SrcAddr + SrcSize - DstAddr; 241 if (Dist <= opts::BackwardDistance) { 242 double Prob = 1.0 - static_cast<double>(Dist) / opts::BackwardDistance; 243 return opts::BackwardWeight * Prob * Count; 244 } 245 return 0; 246 } 247 248 void CacheMetrics::printAll(const std::vector<BinaryFunction *> &BFs) { 249 // Stats related to hot-cold code splitting 250 size_t NumFunctions = 0; 251 size_t NumProfiledFunctions = 0; 252 size_t NumHotFunctions = 0; 253 size_t NumBlocks = 0; 254 size_t NumHotBlocks = 0; 255 256 size_t TotalCodeMinAddr = std::numeric_limits<size_t>::max(); 257 size_t TotalCodeMaxAddr = 0; 258 size_t HotCodeMinAddr = std::numeric_limits<size_t>::max(); 259 size_t HotCodeMaxAddr = 0; 260 261 for (BinaryFunction *BF : BFs) { 262 NumFunctions++; 263 if (BF->hasProfile()) 264 NumProfiledFunctions++; 265 if (BF->hasValidIndex()) 266 NumHotFunctions++; 267 for (BinaryBasicBlock *BB : BF->layout()) { 268 NumBlocks++; 269 size_t BBAddrMin = BB->getOutputAddressRange().first; 270 size_t BBAddrMax = BB->getOutputAddressRange().second; 271 TotalCodeMinAddr = std::min(TotalCodeMinAddr, BBAddrMin); 272 TotalCodeMaxAddr = std::max(TotalCodeMaxAddr, BBAddrMax); 273 if (BF->hasValidIndex() && !BB->isCold()) { 274 NumHotBlocks++; 275 HotCodeMinAddr = std::min(HotCodeMinAddr, BBAddrMin); 276 HotCodeMaxAddr = std::max(HotCodeMaxAddr, BBAddrMax); 277 } 278 } 279 } 280 281 outs() << format(" There are %zu functions;", NumFunctions) 282 << format(" %zu (%.2lf%%) are in the hot section,", NumHotFunctions, 283 100.0 * NumHotFunctions / NumFunctions) 284 << format(" %zu (%.2lf%%) have profile\n", NumProfiledFunctions, 285 100.0 * NumProfiledFunctions / NumFunctions); 286 outs() << format(" There are %zu basic blocks;", NumBlocks) 287 << format(" %zu (%.2lf%%) are in the hot section\n", NumHotBlocks, 288 100.0 * NumHotBlocks / NumBlocks); 289 290 assert(TotalCodeMinAddr <= TotalCodeMaxAddr && "incorrect output addresses"); 291 size_t HotCodeSize = HotCodeMaxAddr - HotCodeMinAddr; 292 size_t TotalCodeSize = TotalCodeMaxAddr - TotalCodeMinAddr; 293 294 size_t HugePage2MB = 2 << 20; 295 outs() << format(" Hot code takes %.2lf%% of binary (%zu bytes out of %zu, " 296 "%.2lf huge pages)\n", 297 100.0 * HotCodeSize / TotalCodeSize, HotCodeSize, 298 TotalCodeSize, double(HotCodeSize) / HugePage2MB); 299 300 // Stats related to expected cache performance 301 std::unordered_map<BinaryBasicBlock *, uint64_t> BBAddr; 302 std::unordered_map<BinaryBasicBlock *, uint64_t> BBSize; 303 extractBasicBlockInfo(BFs, BBAddr, BBSize); 304 305 outs() << " Expected i-TLB cache hit ratio: " 306 << format("%.2lf%%\n", expectedCacheHitRatio(BFs, BBAddr, BBSize)); 307 308 outs() << " TSP score: " 309 << format("%.0lf\n", calcTSPScore(BFs, BBAddr, BBSize)); 310 311 outs() << " ExtTSP score: " 312 << format("%.0lf\n", calcExtTSPScore(BFs, BBAddr, BBSize)); 313 } 314