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:
Timer()187   Timer() {}
Timer(const Timer & other)188   Timer(const Timer &other) : tm(other.tm), handle(other.handle) {}
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, std::move(nameBuilder)))
232               : 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() : timer() {}
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 // DefaultTimingManager
325 //===----------------------------------------------------------------------===//
326 
327 /// Facilities for time measurement and report printing to an output stream.
328 ///
329 /// This is MLIR's default implementation of a `TimingManager`. Prints an
330 /// execution time report upon destruction, or manually through `print()`. By
331 /// default the results are printed in `DisplayMode::Tree` mode to stderr.
332 /// Use `setEnabled(true)` to enable collection of timing samples; it is
333 /// disabled by default.
334 ///
335 /// You should only instantiate a `DefaultTimingManager` if you are writing a
336 /// tool and want to pass a timing manager to the remaining infrastructure. If
337 /// you are writing library or infrastructure code, you should rather accept
338 /// the `TimingManager` base class to allow for users of your code to substitute
339 /// their own timing implementations. Also, if you only intend to collect time
340 /// samples, consider accepting a `Timer` or `TimingScope` instead.
341 class DefaultTimingManager : public TimingManager {
342 public:
343   /// The different display modes for printing the timers.
344   enum class DisplayMode {
345     /// In this mode the results are displayed in a list sorted by total time,
346     /// with timers aggregated into one unique result per timer name.
347     List,
348 
349     /// In this mode the results are displayed in a tree view, with child timers
350     /// nested under their parents.
351     Tree,
352   };
353 
354   DefaultTimingManager();
355   DefaultTimingManager(DefaultTimingManager &&rhs);
356   virtual ~DefaultTimingManager();
357 
358   // Disable copying of the `DefaultTimingManager`.
359   DefaultTimingManager(const DefaultTimingManager &rhs) = delete;
360   DefaultTimingManager &operator=(const DefaultTimingManager &rhs) = delete;
361 
362   /// Enable or disable execution time sampling.
363   void setEnabled(bool enabled);
364 
365   /// Return whether execution time sampling is enabled.
366   bool isEnabled() const;
367 
368   /// Change the display mode.
369   void setDisplayMode(DisplayMode displayMode);
370 
371   /// Return the current display mode;
372   DisplayMode getDisplayMode() const;
373 
374   /// Change the stream where the output will be printed to.
375   void setOutput(raw_ostream &os);
376 
377   /// Return the current output stream where the output will be printed to.
378   raw_ostream &getOutput() const;
379 
380   /// Print and clear the timing results. Only call this when there are no more
381   /// references to nested timers around, as printing post-processes and clears
382   /// the timers.
383   void print();
384 
385   /// Clear the timing results. Only call this when there are no more references
386   /// to nested timers around, as clearing invalidates them.
387   void clear();
388 
389   /// Debug print the timer data structures to an output stream.
390   void dumpTimers(raw_ostream &os = llvm::errs());
391 
392   /// Debug print the timers as a list. Only call this when there are no more
393   /// references to nested timers around.
394   void dumpAsList(raw_ostream &os = llvm::errs());
395 
396   /// Debug print the timers as a tree. Only call this when there are no
397   /// more references to nested timers around.
398   void dumpAsTree(raw_ostream &os = llvm::errs());
399 
400 protected:
401   // `TimingManager` callbacks
402   Optional<void *> rootTimer() override;
403   void startTimer(void *handle) override;
404   void stopTimer(void *handle) override;
405   void *nestTimer(void *handle, const void *id,
406                   function_ref<std::string()> nameBuilder) override;
407   void hideTimer(void *handle) override;
408 
409 private:
410   const std::unique_ptr<detail::DefaultTimingManagerImpl> impl;
411 };
412 
413 /// Register a set of useful command-line options that can be used to configure
414 /// a `DefaultTimingManager`. The values of these options can be applied via the
415 /// `applyDefaultTimingManagerCLOptions` method.
416 void registerDefaultTimingManagerCLOptions();
417 
418 /// Apply any values that were registered with
419 /// 'registerDefaultTimingManagerOptions' to a `DefaultTimingManager`.
420 void applyDefaultTimingManagerCLOptions(DefaultTimingManager &tm);
421 
422 } // namespace mlir
423 
424 #endif // MLIR_SUPPORT_TIMING_H
425