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