1 //===- PassTiming.cpp -----------------------------------------------------===// 2 // 3 // Copyright 2019 The MLIR Authors. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 // ============================================================================= 17 18 #include "PassDetail.h" 19 #include "mlir/Pass/PassManager.h" 20 #include "llvm/ADT/MapVector.h" 21 #include "llvm/ADT/STLExtras.h" 22 #include "llvm/ADT/SmallVector.h" 23 #include "llvm/ADT/Statistic.h" 24 #include "llvm/Support/Format.h" 25 #include "llvm/Support/FormatVariadic.h" 26 #include "llvm/Support/Threading.h" 27 #include <chrono> 28 29 using namespace mlir; 30 using namespace mlir::detail; 31 32 constexpr llvm::StringLiteral kPassTimingDescription = 33 "... Pass execution timing report ..."; 34 35 namespace { 36 /// Simple record class to record timing information. 37 struct TimeRecord { 38 TimeRecord(double wall = 0.0, double user = 0.0) : wall(wall), user(user) {} 39 40 TimeRecord &operator+=(const TimeRecord &other) { 41 wall += other.wall; 42 user += other.user; 43 return *this; 44 } 45 46 /// Print the current time record to 'os', with a breakdown showing 47 /// contributions to the give 'total' time record. 48 void print(raw_ostream &os, const TimeRecord &total) { 49 if (total.user != total.wall) 50 os << llvm::format(" %7.4f (%5.1f%%) ", user, 51 100.0 * user / total.user); 52 os << llvm::format(" %7.4f (%5.1f%%) ", wall, 100.0 * wall / total.wall); 53 } 54 55 double wall, user; 56 }; 57 58 /// An enumeration of the different types of timers. 59 enum class TimerKind { 60 /// This timer represents an ordered collection of pass timers, corresponding 61 /// to a pass pipeline. 62 Pipeline, 63 64 /// This timer represents a collection of pipeline timers. 65 PipelineCollection, 66 67 /// This timer represents an analysis or pass timer. 68 PassOrAnalysis 69 }; 70 71 struct Timer { 72 explicit Timer(std::string &&name, TimerKind kind) 73 : name(std::move(name)), kind(kind) {} 74 75 /// Start the timer. 76 void start() { startTime = std::chrono::system_clock::now(); } 77 78 /// Stop the timer. 79 void stop() { 80 auto newTime = std::chrono::system_clock::now() - startTime; 81 wallTime += newTime; 82 userTime += newTime; 83 } 84 85 /// Get or create a child timer with the provided name and id. 86 Timer *getChildTimer(const void *id, TimerKind kind, 87 std::function<std::string()> &&nameBuilder) { 88 auto &child = children[id]; 89 if (!child) 90 child = std::make_unique<Timer>(nameBuilder(), kind); 91 return child.get(); 92 } 93 94 /// Returns the total time for this timer in seconds. 95 TimeRecord getTotalTime() { 96 // If this is a pass or analysis timer, use the recorded time directly. 97 if (kind == TimerKind::PassOrAnalysis) { 98 return TimeRecord( 99 std::chrono::duration_cast<std::chrono::duration<double>>(wallTime) 100 .count(), 101 std::chrono::duration_cast<std::chrono::duration<double>>(userTime) 102 .count()); 103 } 104 105 // Otheriwse, accumulate the timing from each of the children. 106 TimeRecord totalTime; 107 for (auto &child : children) 108 totalTime += child.second->getTotalTime(); 109 return totalTime; 110 } 111 112 /// A map of unique identifiers to child timers. 113 using ChildrenMap = llvm::MapVector<const void *, std::unique_ptr<Timer>>; 114 115 /// Merge the timing data from 'other' into this timer. 116 void merge(Timer &&other) { 117 if (wallTime < other.wallTime) 118 wallTime = other.wallTime; 119 userTime += other.userTime; 120 mergeChildren(std::move(other.children)); 121 } 122 123 /// Merge the timer chilren in 'otherChildren' with the children of this 124 /// timer. 125 void mergeChildren(ChildrenMap &&otherChildren) { 126 // Check for an empty children list. 127 if (children.empty()) { 128 children = std::move(otherChildren); 129 return; 130 } 131 132 // Pipeline merges are handled separately as the children are merged 133 // lexographically. 134 if (kind == TimerKind::Pipeline) { 135 assert(children.size() == otherChildren.size() && 136 "pipeline merge requires the same number of children"); 137 for (auto it : llvm::zip(children, otherChildren)) 138 std::get<0>(it).second->merge(std::move(*std::get<1>(it).second)); 139 return; 140 } 141 142 // Otherwise, we merge children based upon their timer key. 143 for (auto &otherChild : otherChildren) 144 mergeChild(std::move(otherChild)); 145 } 146 147 /// Merge in the given child timer and id into this timer. 148 void mergeChild(ChildrenMap::value_type &&childIt) { 149 auto &child = children[childIt.first]; 150 if (!child) 151 child = std::move(childIt.second); 152 else 153 child->merge(std::move(*childIt.second)); 154 } 155 156 /// Raw timing information. 157 std::chrono::time_point<std::chrono::system_clock> startTime; 158 std::chrono::nanoseconds wallTime = std::chrono::nanoseconds(0); 159 std::chrono::nanoseconds userTime = std::chrono::nanoseconds(0); 160 161 /// A map of unique identifiers to child timers. 162 ChildrenMap children; 163 164 /// A descriptive name for this timer. 165 std::string name; 166 167 /// The type of timer this instance represents. 168 TimerKind kind; 169 }; 170 171 struct PassTiming : public PassInstrumentation { 172 PassTiming(PassTimingDisplayMode displayMode) : displayMode(displayMode) {} 173 ~PassTiming() override { print(); } 174 175 /// Setup the instrumentation hooks. 176 void runBeforePipeline(const OperationName &name, 177 const PipelineParentInfo &parentInfo) override; 178 void runAfterPipeline(const OperationName &name, 179 const PipelineParentInfo &parentInfo) override; 180 void runBeforePass(Pass *pass, Operation *) override { startPassTimer(pass); } 181 void runAfterPass(Pass *pass, Operation *) override; 182 void runAfterPassFailed(Pass *pass, Operation *op) override { 183 runAfterPass(pass, op); 184 } 185 void runBeforeAnalysis(llvm::StringRef name, AnalysisID *id, 186 Operation *) override { 187 startAnalysisTimer(name, id); 188 } 189 void runAfterAnalysis(llvm::StringRef, AnalysisID *, Operation *) override; 190 191 /// Print and clear the timing results. 192 void print(); 193 194 /// Start a new timer for the given pass. 195 void startPassTimer(Pass *pass); 196 197 /// Start a new timer for the given analysis. 198 void startAnalysisTimer(llvm::StringRef name, AnalysisID *id); 199 200 /// Pop the last active timer for the current thread. 201 Timer *popLastActiveTimer() { 202 auto tid = llvm::get_threadid(); 203 auto &activeTimers = activeThreadTimers[tid]; 204 assert(!activeTimers.empty() && "expected active timer"); 205 return activeTimers.pop_back_val(); 206 } 207 208 /// Print the timing result in list mode. 209 void printResultsAsList(raw_ostream &os, Timer *root, TimeRecord totalTime); 210 211 /// Print the timing result in pipeline mode. 212 void printResultsAsPipeline(raw_ostream &os, Timer *root, 213 TimeRecord totalTime); 214 215 /// Returns a timer for the provided identifier and name. 216 Timer *getTimer(const void *id, TimerKind kind, 217 std::function<std::string()> &&nameBuilder) { 218 auto tid = llvm::get_threadid(); 219 220 // If there is no active timer then add to the root timer. 221 auto &activeTimers = activeThreadTimers[tid]; 222 Timer *parentTimer; 223 if (activeTimers.empty()) { 224 auto &rootTimer = rootTimers[tid]; 225 if (!rootTimer) 226 rootTimer = std::make_unique<Timer>("root", TimerKind::Pipeline); 227 parentTimer = rootTimer.get(); 228 } else { 229 // Otherwise, add this to the active timer. 230 parentTimer = activeTimers.back(); 231 } 232 233 auto timer = parentTimer->getChildTimer(id, kind, std::move(nameBuilder)); 234 activeTimers.push_back(timer); 235 return timer; 236 } 237 238 /// The root top level timers for each thread. 239 DenseMap<uint64_t, std::unique_ptr<Timer>> rootTimers; 240 241 /// A stack of the currently active pass timers per thread. 242 DenseMap<uint64_t, SmallVector<Timer *, 4>> activeThreadTimers; 243 244 /// The display mode to use when printing the timing results. 245 PassTimingDisplayMode displayMode; 246 247 /// A mapping of pipeline timers that need to be merged into the parent 248 /// collection. The timers are mapped to the parent info to merge into. 249 DenseMap<PipelineParentInfo, SmallVector<Timer::ChildrenMap::value_type, 4>> 250 pipelinesToMerge; 251 }; 252 } // end anonymous namespace 253 254 void PassTiming::runBeforePipeline(const OperationName &name, 255 const PipelineParentInfo &parentInfo) { 256 // We don't actually want to time the piplelines, they gather their total 257 // from their held passes. 258 getTimer(name.getAsOpaquePointer(), TimerKind::Pipeline, 259 [&] { return ("'" + name.getStringRef() + "' Pipeline").str(); }); 260 } 261 262 void PassTiming::runAfterPipeline(const OperationName &name, 263 const PipelineParentInfo &parentInfo) { 264 // Pop the timer for the pipeline. 265 auto tid = llvm::get_threadid(); 266 auto &activeTimers = activeThreadTimers[tid]; 267 assert(!activeTimers.empty() && "expected active timer"); 268 activeTimers.pop_back(); 269 270 // If the current thread is the same as the parent, there is nothing left to 271 // do. 272 if (tid == parentInfo.parentThreadID) 273 return; 274 275 // Otherwise, mark the pipeline timer for merging into the correct parent 276 // thread. 277 assert(activeTimers.empty() && "expected parent timer to be root"); 278 auto *parentTimer = rootTimers[tid].get(); 279 assert(parentTimer->children.size() == 1 && 280 parentTimer->children.count(name.getAsOpaquePointer()) && 281 "expected a single pipeline timer"); 282 pipelinesToMerge[parentInfo].push_back( 283 std::move(*parentTimer->children.begin())); 284 rootTimers.erase(tid); 285 } 286 287 /// Start a new timer for the given pass. 288 void PassTiming::startPassTimer(Pass *pass) { 289 auto kind = isAdaptorPass(pass) ? TimerKind::PipelineCollection 290 : TimerKind::PassOrAnalysis; 291 Timer *timer = getTimer(pass, kind, [pass]() -> std::string { 292 if (auto *adaptor = getAdaptorPassBase(pass)) { 293 std::string name = "Pipeline Collection : ["; 294 llvm::raw_string_ostream os(name); 295 interleaveComma(adaptor->getPassManagers(), os, [&](OpPassManager &pm) { 296 os << '\'' << pm.getOpName() << '\''; 297 }); 298 os << ']'; 299 return os.str(); 300 } 301 return pass->getName(); 302 }); 303 304 // We don't actually want to time the adaptor passes, they gather their total 305 // from their held passes. 306 if (!isAdaptorPass(pass)) 307 timer->start(); 308 } 309 310 /// Start a new timer for the given analysis. 311 void PassTiming::startAnalysisTimer(llvm::StringRef name, AnalysisID *id) { 312 Timer *timer = getTimer(id, TimerKind::PassOrAnalysis, 313 [name] { return "(A) " + name.str(); }); 314 timer->start(); 315 } 316 317 /// Stop a pass timer. 318 void PassTiming::runAfterPass(Pass *pass, Operation *) { 319 Timer *timer = popLastActiveTimer(); 320 321 // If this is an OpToOpPassAdaptorParallel, then we need to merge in the 322 // timing data for the pipelines running on other threads. 323 if (isa<OpToOpPassAdaptorParallel>(pass)) { 324 auto toMerge = pipelinesToMerge.find({llvm::get_threadid(), pass}); 325 if (toMerge != pipelinesToMerge.end()) { 326 for (auto &it : toMerge->second) 327 timer->mergeChild(std::move(it)); 328 pipelinesToMerge.erase(toMerge); 329 } 330 return; 331 } 332 333 // Adapator passes aren't timed directly, so we don't need to stop their 334 // timers. 335 if (!isAdaptorPass(pass)) 336 timer->stop(); 337 } 338 339 /// Stop a timer. 340 void PassTiming::runAfterAnalysis(llvm::StringRef, AnalysisID *, Operation *) { 341 popLastActiveTimer()->stop(); 342 } 343 344 /// Utility to print the timer heading information. 345 static void printTimerHeader(llvm::raw_ostream &os, TimeRecord total) { 346 os << "===" << std::string(73, '-') << "===\n"; 347 // Figure out how many spaces to description name. 348 unsigned Padding = (80 - kPassTimingDescription.size()) / 2; 349 os.indent(Padding) << kPassTimingDescription << '\n'; 350 os << "===" << std::string(73, '-') << "===\n"; 351 352 // Print the total time followed by the section headers. 353 os << llvm::format(" Total Execution Time: %5.4f seconds\n\n", total.wall); 354 if (total.user != total.wall) 355 os << " ---User Time---"; 356 os << " ---Wall Time--- --- Name ---\n"; 357 } 358 359 /// Utility to print a single line entry in the timer output. 360 static void printTimeEntry(raw_ostream &os, unsigned indent, StringRef name, 361 TimeRecord time, TimeRecord totalTime) { 362 time.print(os, totalTime); 363 os.indent(indent) << name << "\n"; 364 } 365 366 /// Print out the current timing information. 367 void PassTiming::print() { 368 // Don't print anything if there is no timing data. 369 if (rootTimers.empty()) 370 return; 371 372 assert(rootTimers.size() == 1 && "expected one remaining root timer"); 373 auto &rootTimer = rootTimers.begin()->second; 374 auto os = llvm::CreateInfoOutputFile(); 375 376 // Print the timer header. 377 TimeRecord totalTime = rootTimer->getTotalTime(); 378 printTimerHeader(*os, totalTime); 379 380 // Defer to a specialized printer for each display mode. 381 switch (displayMode) { 382 case PassTimingDisplayMode::List: 383 printResultsAsList(*os, rootTimer.get(), totalTime); 384 break; 385 case PassTimingDisplayMode::Pipeline: 386 printResultsAsPipeline(*os, rootTimer.get(), totalTime); 387 break; 388 } 389 printTimeEntry(*os, 0, "Total", totalTime, totalTime); 390 os->flush(); 391 392 // Reset root timers. 393 rootTimers.clear(); 394 activeThreadTimers.clear(); 395 } 396 397 /// Print the timing result in list mode. 398 void PassTiming::printResultsAsList(raw_ostream &os, Timer *root, 399 TimeRecord totalTime) { 400 llvm::StringMap<TimeRecord> mergedTimings; 401 402 std::function<void(Timer *)> addTimer = [&](Timer *timer) { 403 // Only add timing information for passes and analyses. 404 if (timer->kind == TimerKind::PassOrAnalysis) 405 mergedTimings[timer->name] += timer->getTotalTime(); 406 for (auto &children : timer->children) 407 addTimer(children.second.get()); 408 }; 409 410 // Add each of the top level timers. 411 for (auto &topLevelTimer : root->children) 412 addTimer(topLevelTimer.second.get()); 413 414 // Sort the timing information by wall time. 415 std::vector<std::pair<StringRef, TimeRecord>> timerNameAndTime; 416 for (auto &it : mergedTimings) 417 timerNameAndTime.emplace_back(it.first(), it.second); 418 llvm::array_pod_sort(timerNameAndTime.begin(), timerNameAndTime.end(), 419 [](const std::pair<StringRef, TimeRecord> *lhs, 420 const std::pair<StringRef, TimeRecord> *rhs) { 421 return llvm::array_pod_sort_comparator<double>( 422 &rhs->second.wall, &lhs->second.wall); 423 }); 424 425 // Print the timing information sequentially. 426 for (auto &timeData : timerNameAndTime) 427 printTimeEntry(os, 0, timeData.first, timeData.second, totalTime); 428 } 429 430 /// Print the timing result in pipeline mode. 431 void PassTiming::printResultsAsPipeline(raw_ostream &os, Timer *root, 432 TimeRecord totalTime) { 433 std::function<void(unsigned, Timer *)> printTimer = [&](unsigned indent, 434 Timer *timer) { 435 // If this is a timer for a pipeline collection and the collection only has 436 // one pipeline child, then only print the child. 437 if (timer->kind == TimerKind::PipelineCollection && 438 timer->children.size() == 1) 439 return printTimer(indent, timer->children.begin()->second.get()); 440 441 printTimeEntry(os, indent, timer->name, timer->getTotalTime(), totalTime); 442 443 // If this timer is a pipeline, then print the children in-order. 444 if (timer->kind == TimerKind::Pipeline) { 445 for (auto &child : timer->children) 446 printTimer(indent + 2, child.second.get()); 447 return; 448 } 449 450 // Otherwise, sort the children by name to give a deterministic ordering 451 // when emitting the time. 452 SmallVector<Timer *, 4> children; 453 children.reserve(timer->children.size()); 454 for (auto &child : timer->children) 455 children.push_back(child.second.get()); 456 llvm::array_pod_sort(children.begin(), children.end(), 457 [](Timer *const *lhs, Timer *const *rhs) { 458 return (*lhs)->name.compare((*rhs)->name); 459 }); 460 for (auto &child : children) 461 printTimer(indent + 2, child); 462 }; 463 464 // Print each of the top level timers. 465 for (auto &topLevelTimer : root->children) 466 printTimer(0, topLevelTimer.second.get()); 467 } 468 469 //===----------------------------------------------------------------------===// 470 // PassManager 471 //===----------------------------------------------------------------------===// 472 473 /// Add an instrumentation to time the execution of passes and the computation 474 /// of analyses. 475 void PassManager::enableTiming(PassTimingDisplayMode displayMode) { 476 // Check if pass timing is already enabled. 477 if (passTiming) 478 return; 479 addInstrumentation(std::make_unique<PassTiming>(displayMode)); 480 passTiming = true; 481 } 482