xref: /llvm-project/mlir/include/mlir/Support/Timing.h (revision 362aa434cc31ccca96749a6db8cd97f5b7d71206)
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