1 //===- Timing.h - Execution time measurement facilities ---------*- C++ -*-===// 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 // Facilities to measure and provide statistics on execution time. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #ifndef MLIR_SUPPORT_TIMING_H 14 #define MLIR_SUPPORT_TIMING_H 15 16 #include "mlir/Support/LLVM.h" 17 #include "llvm/ADT/STLExtras.h" 18 #include "llvm/ADT/StringMapEntry.h" 19 #include "llvm/Support/raw_ostream.h" 20 #include <optional> 21 22 namespace mlir { 23 24 class Timer; 25 class TimingManager; 26 class TimingScope; 27 class DefaultTimingManager; 28 namespace detail { 29 class TimingManagerImpl; 30 class DefaultTimingManagerImpl; 31 } // namespace detail 32 33 //===----------------------------------------------------------------------===// 34 // TimingIdentifier 35 //===----------------------------------------------------------------------===// 36 37 /// This class represesents a uniqued string owned by a `TimingManager`. Most 38 /// importantly, instances of this class provide a stable opaque pointer that 39 /// is guaranteed to be reproduced by later interning of the same string. The 40 /// `TimingManager` uses this mechanism to provide timers with an opaque id 41 /// even when the user of the API merely provided a string as identification 42 /// (instead of a pass for example). 43 /// 44 /// This is a POD type with pointer size, so it should be passed around by 45 /// value. The underlying data is owned by the `TimingManager`. 46 class TimingIdentifier { 47 using EntryType = llvm::StringMapEntry<std::nullopt_t>; 48 49 public: 50 TimingIdentifier(const TimingIdentifier &) = default; 51 TimingIdentifier &operator=(const TimingIdentifier &other) = default; 52 53 /// Return an identifier for the specified string. 54 static TimingIdentifier get(StringRef str, TimingManager &tm); 55 56 /// Return a `StringRef` for the string. strref()57 StringRef strref() const { return entry->first(); } 58 59 /// Return an `std::string`. str()60 std::string str() const { return strref().str(); } 61 62 /// Return the opaque pointer that corresponds to this identifier. getAsOpaquePointer()63 const void *getAsOpaquePointer() const { 64 return static_cast<const void *>(entry); 65 } 66 67 private: 68 const EntryType *entry; TimingIdentifier(const EntryType * entry)69 explicit TimingIdentifier(const EntryType *entry) : entry(entry) {} 70 }; 71 72 //===----------------------------------------------------------------------===// 73 // TimingManager 74 //===----------------------------------------------------------------------===// 75 76 /// This class represents facilities to measure execution time. 77 /// 78 /// Libraries and infrastructure code operate on opque `Timer` handles returned 79 /// by various functions of this manager. Timers are started and stopped to 80 /// demarcate regions in the code where execution time is of interest, and they 81 /// can be nested to provide more detailed timing resolution. Calls to the timer 82 /// start, stop, and nesting functions must be balanced. To facilitate this, 83 /// users are encouraged to leverage the `TimingScope` RAII-style wrapper around 84 /// `Timer`s. 85 /// 86 /// Users can provide their own implementation of `TimingManager`, or use the 87 /// default `DefaultTimingManager` implementation in MLIR. Implementations 88 /// override the various protected virtual functions to create, nest, start, and 89 /// stop timers. A common pattern is for subclasses to provide a custom timer 90 /// class and simply pass pointers to instances of this class around as the 91 /// opaque timer handle. The manager itself can then forward callbacks to the 92 /// this class. Alternatively, external timing libraries may return their own 93 /// opaque handles for timing scopes. 94 /// 95 /// For example: 96 /// ``` 97 /// void doWork(TimingManager &tm) { 98 /// auto root = tm.getRootScope(); 99 /// 100 /// { 101 /// auto scope = root.nest("First"); 102 /// doSomeWork(); 103 /// // <-- "First" timer stops here 104 /// } 105 /// 106 /// auto scope = root.nest("Second"); 107 /// doEvenMoreWork(); 108 /// scope.stop(); // <-- "Second" timer stops here 109 /// 110 /// // <-- Root timer stops here 111 /// } 112 /// ``` 113 class TimingManager { 114 public: 115 explicit TimingManager(); 116 virtual ~TimingManager(); 117 118 /// Get the root timer of this timing manager. The returned timer must be 119 /// started and stopped manually. Execution time can be measured by nesting 120 /// timers within this root timer and starting/stopping them as appropriate. 121 /// Use this function only if you need access to the timer itself. Otherwise 122 /// consider the more convenient `getRootScope()` which offers an RAII-style 123 /// wrapper around the timer. 124 Timer getRootTimer(); 125 126 /// Get the root timer of this timing manager wrapped in a `TimingScope` for 127 /// convenience. Automatically starts the timer and stops it as soon as the 128 /// `TimingScope` is destroyed, e.g. when it goes out of scope. 129 TimingScope getRootScope(); 130 131 protected: 132 // Allow `Timer` access to the protected callbacks. 133 friend class Timer; 134 135 //===--------------------------------------------------------------------===// 136 // Callbacks 137 // 138 // See the corresponding functions in `Timer` for additional details. 139 140 /// Return the root timer. Implementations should return `std::nullopt` if the 141 /// collection of timing samples is disabled. This will cause the timers 142 /// constructed from the manager to be tombstones which can be skipped 143 /// quickly. 144 virtual std::optional<void *> rootTimer() = 0; 145 146 /// Start the timer with the given handle. 147 virtual void startTimer(void *handle) = 0; 148 149 /// Stop the timer with the given handle. 150 virtual void stopTimer(void *handle) = 0; 151 152 /// Create a child timer nested within the one with the given handle. The `id` 153 /// parameter is used to uniquely identify the timer within its parent. 154 /// Multiple calls to this function with the same `handle` and `id` should 155 /// return the same timer, or at least cause the samples of the returned 156 /// timers to be combined for the final timing results. 157 virtual void *nestTimer(void *handle, const void *id, 158 function_ref<std::string()> nameBuilder) = 0; 159 160 /// Hide the timer in timing reports and directly show its children. This is 161 /// merely a hint that implementations are free to ignore. hideTimer(void * handle)162 virtual void hideTimer(void *handle) {} 163 164 protected: 165 const std::unique_ptr<detail::TimingManagerImpl> impl; 166 167 // Allow `TimingIdentifier::get` access to the private impl details. 168 friend class TimingIdentifier; 169 170 private: 171 // Disallow copying the manager. 172 TimingManager(const TimingManager &) = delete; 173 void operator=(const TimingManager &) = delete; 174 }; 175 176 //===----------------------------------------------------------------------===// 177 // Timer 178 //===----------------------------------------------------------------------===// 179 180 /// A handle for a timer in a `TimingManager`. 181 /// 182 /// This class encapsulates a pointer to a `TimingManager` and an opaque handle 183 /// to a timer running within that manager. Libraries and infrastructure code 184 /// operate on `Timer` rather than any concrete classes handed out by custom 185 /// manager implementations. 186 class Timer { 187 public: 188 Timer() = default; 189 Timer(const Timer &other) = default; Timer(Timer && other)190 Timer(Timer &&other) : Timer(other) { 191 other.tm = nullptr; 192 other.handle = nullptr; 193 } 194 195 Timer &operator=(Timer &&other) { 196 tm = other.tm; 197 handle = other.handle; 198 other.tm = nullptr; 199 other.handle = nullptr; 200 return *this; 201 } 202 203 /// Returns whether this is a valid timer handle. Invalid timer handles are 204 /// used when timing is disabled in the `TimingManager` to keep the impact on 205 /// performance low. 206 explicit operator bool() const { return tm != nullptr; } 207 208 /// Start the timer. This must be accompanied by a corresponding call to 209 /// `stop()` at a later point. start()210 void start() { 211 if (tm) 212 tm->startTimer(handle); 213 } 214 215 /// Stop the timer. This must have been preceded by a corresponding call to 216 /// `start()` at an earlier point. stop()217 void stop() { 218 if (tm) 219 tm->stopTimer(handle); 220 } 221 222 /// Create a child timer nested within this one. Multiple calls to this 223 /// function with the same unique identifier `id` will return the same child 224 /// timer. The timer must have been started when calling this function. 225 /// 226 /// This function can be called from other threads, as long as this timer 227 /// is not stopped before any uses of the child timer on the other thread are 228 /// stopped. 229 /// 230 /// The `nameBuilder` function is not guaranteed to be called. nest(const void * id,function_ref<std::string ()> nameBuilder)231 Timer nest(const void *id, function_ref<std::string()> nameBuilder) { 232 return tm ? Timer(*tm, tm->nestTimer(handle, id, nameBuilder)) : Timer(); 233 } 234 235 /// See above. nest(TimingIdentifier name)236 Timer nest(TimingIdentifier name) { 237 return tm ? nest(name.getAsOpaquePointer(), [=]() { return name.str(); }) 238 : Timer(); 239 } 240 241 /// See above. nest(StringRef name)242 Timer nest(StringRef name) { 243 return tm ? nest(TimingIdentifier::get(name, *tm)) : Timer(); 244 } 245 246 /// Hide the timer in timing reports and directly show its children. hide()247 void hide() { 248 if (tm) 249 tm->hideTimer(handle); 250 } 251 252 protected: Timer(TimingManager & tm,void * handle)253 Timer(TimingManager &tm, void *handle) : tm(&tm), handle(handle) {} 254 255 // Allow the `TimingManager` access to the above constructor. 256 friend class TimingManager; 257 258 private: 259 /// The associated timing manager. 260 TimingManager *tm = nullptr; 261 /// An opaque handle that identifies the timer in the timing manager 262 /// implementation. 263 void *handle = nullptr; 264 }; 265 266 //===----------------------------------------------------------------------===// 267 // TimingScope 268 //===----------------------------------------------------------------------===// 269 270 /// An RAII-style wrapper around a timer that ensures the timer is properly 271 /// started and stopped. 272 class TimingScope { 273 public: TimingScope()274 TimingScope() {} TimingScope(const Timer & other)275 TimingScope(const Timer &other) : timer(other) { 276 if (timer) 277 timer.start(); 278 } TimingScope(Timer && other)279 TimingScope(Timer &&other) : timer(std::move(other)) { 280 if (timer) 281 timer.start(); 282 } TimingScope(TimingScope && other)283 TimingScope(TimingScope &&other) : timer(std::move(other.timer)) {} ~TimingScope()284 ~TimingScope() { stop(); } 285 286 TimingScope &operator=(TimingScope &&other) { 287 stop(); 288 timer = std::move(other.timer); 289 return *this; 290 } 291 292 /// Check if the timing scope actually contains a valid timer. 293 explicit operator bool() const { return bool(timer); } 294 295 // Disable copying of the `TimingScope`. 296 TimingScope(const TimingScope &) = delete; 297 TimingScope &operator=(const TimingScope &) = delete; 298 299 /// Manually stop the timer early. stop()300 void stop() { 301 timer.stop(); 302 timer = Timer(); 303 } 304 305 /// Create a nested timing scope. 306 /// 307 /// This returns a new `TimingScope` with a timer nested within the current 308 /// scope. In this fashion, the time in this scope may be further subdivided 309 /// in a more fine-grained fashion. 310 template <typename... Args> nest(Args...args)311 TimingScope nest(Args... args) { 312 return TimingScope(std::move(timer.nest(std::forward<Args>(args)...))); 313 } 314 315 /// Hide the timer in timing reports and directly show its children. hide()316 void hide() { timer.hide(); } 317 318 private: 319 /// The wrapped timer. 320 Timer timer; 321 }; 322 323 //===----------------------------------------------------------------------===// 324 // OutputStrategy 325 //===----------------------------------------------------------------------===// 326 327 /// Simple record class to record timing information. 328 struct TimeRecord { wallTimeRecord329 TimeRecord(double wall = 0.0, double user = 0.0) : wall(wall), user(user) {} 330 331 TimeRecord &operator+=(const TimeRecord &other) { 332 wall += other.wall; 333 user += other.user; 334 return *this; 335 } 336 337 TimeRecord &operator-=(const TimeRecord &other) { 338 wall -= other.wall; 339 user -= other.user; 340 return *this; 341 } 342 343 double wall, user; 344 }; 345 346 /// Facilities for printing timing reports to various output formats. 347 /// 348 /// This is an abstract class that serves as the foundation for printing. 349 /// Users can implement additional output formats by extending this abstract 350 /// class. 351 class OutputStrategy { 352 public: OutputStrategy(raw_ostream & os)353 OutputStrategy(raw_ostream &os) : os(os) {} 354 virtual ~OutputStrategy() = default; 355 356 virtual void printHeader(const TimeRecord &total) = 0; 357 virtual void printFooter() = 0; 358 virtual void printTime(const TimeRecord &time, const TimeRecord &total) = 0; 359 virtual void printListEntry(StringRef name, const TimeRecord &time, 360 const TimeRecord &total, 361 bool lastEntry = false) = 0; 362 virtual void printTreeEntry(unsigned indent, StringRef name, 363 const TimeRecord &time, 364 const TimeRecord &total) = 0; 365 virtual void printTreeEntryEnd(unsigned indent, bool lastEntry = false) = 0; 366 367 raw_ostream &os; 368 }; 369 370 //===----------------------------------------------------------------------===// 371 // DefaultTimingManager 372 //===----------------------------------------------------------------------===// 373 374 /// Facilities for time measurement and report printing to an output stream. 375 /// 376 /// This is MLIR's default implementation of a `TimingManager`. Prints an 377 /// execution time report upon destruction, or manually through `print()`. By 378 /// default the results are printed in `DisplayMode::Tree` mode to stderr. 379 /// Use `setEnabled(true)` to enable collection of timing samples; it is 380 /// disabled by default. 381 /// 382 /// You should only instantiate a `DefaultTimingManager` if you are writing a 383 /// tool and want to pass a timing manager to the remaining infrastructure. If 384 /// you are writing library or infrastructure code, you should rather accept 385 /// the `TimingManager` base class to allow for users of your code to substitute 386 /// their own timing implementations. Also, if you only intend to collect time 387 /// samples, consider accepting a `Timer` or `TimingScope` instead. 388 class DefaultTimingManager : public TimingManager { 389 public: 390 /// The different display modes for printing the timers. 391 enum class DisplayMode { 392 /// In this mode the results are displayed in a list sorted by total time, 393 /// with timers aggregated into one unique result per timer name. 394 List, 395 396 /// In this mode the results are displayed in a tree view, with child timers 397 /// nested under their parents. 398 Tree, 399 }; 400 401 /// The different output formats for printing the timers. 402 enum class OutputFormat { 403 /// In this format the results are displayed in text format. 404 Text, 405 406 /// In this format the results are displayed in JSON format. 407 Json, 408 }; 409 410 DefaultTimingManager(); 411 DefaultTimingManager(DefaultTimingManager &&rhs); 412 ~DefaultTimingManager() override; 413 414 // Disable copying of the `DefaultTimingManager`. 415 DefaultTimingManager(const DefaultTimingManager &rhs) = delete; 416 DefaultTimingManager &operator=(const DefaultTimingManager &rhs) = delete; 417 418 /// Enable or disable execution time sampling. 419 void setEnabled(bool enabled); 420 421 /// Return whether execution time sampling is enabled. 422 bool isEnabled() const; 423 424 /// Change the display mode. 425 void setDisplayMode(DisplayMode displayMode); 426 427 /// Return the current display mode; 428 DisplayMode getDisplayMode() const; 429 430 /// Change the stream where the output will be printed to. 431 void setOutput(std::unique_ptr<OutputStrategy> output); 432 433 /// Print and clear the timing results. Only call this when there are no more 434 /// references to nested timers around, as printing post-processes and clears 435 /// the timers. 436 void print(); 437 438 /// Clear the timing results. Only call this when there are no more references 439 /// to nested timers around, as clearing invalidates them. 440 void clear(); 441 442 /// Debug print the timer data structures to an output stream. 443 void dumpTimers(raw_ostream &os = llvm::errs()); 444 445 /// Debug print the timers as a list. Only call this when there are no more 446 /// references to nested timers around. 447 void dumpAsList(raw_ostream &os = llvm::errs()); 448 449 /// Debug print the timers as a tree. Only call this when there are no 450 /// more references to nested timers around. 451 void dumpAsTree(raw_ostream &os = llvm::errs()); 452 453 protected: 454 // `TimingManager` callbacks 455 std::optional<void *> rootTimer() override; 456 void startTimer(void *handle) override; 457 void stopTimer(void *handle) override; 458 void *nestTimer(void *handle, const void *id, 459 function_ref<std::string()> nameBuilder) override; 460 void hideTimer(void *handle) override; 461 462 private: 463 const std::unique_ptr<detail::DefaultTimingManagerImpl> impl; 464 std::unique_ptr<OutputStrategy> out; 465 }; 466 467 /// Register a set of useful command-line options that can be used to configure 468 /// a `DefaultTimingManager`. The values of these options can be applied via the 469 /// `applyDefaultTimingManagerCLOptions` method. 470 void registerDefaultTimingManagerCLOptions(); 471 472 /// Apply any values that were registered with 473 /// 'registerDefaultTimingManagerOptions' to a `DefaultTimingManager`. 474 void applyDefaultTimingManagerCLOptions(DefaultTimingManager &tm); 475 476 } // namespace mlir 477 478 #endif // MLIR_SUPPORT_TIMING_H 479