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
22namespace mlir {
23
24class Timer;
25class TimingManager;
26class TimingScope;
27class DefaultTimingManager;
28namespace detail {
29class TimingManagerImpl;
30class 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`.
46class TimingIdentifier {
47 using EntryType = llvm::StringMapEntry<std::nullopt_t>;
48
49public:
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.
57 StringRef strref() const { return entry->first(); }
58
59 /// Return an `std::string`.
60 std::string str() const { return strref().str(); }
61
62 /// Return the opaque pointer that corresponds to this identifier.
63 const void *getAsOpaquePointer() const {
64 return static_cast<const void *>(entry);
65 }
66
67private:
68 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/// ```
113class TimingManager {
114public:
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
131protected:
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.
162 virtual void hideTimer(void *handle) {}
163
164protected:
165 const std::unique_ptr<detail::TimingManagerImpl> impl;
166
167 // Allow `TimingIdentifier::get` access to the private impl details.
168 friend class TimingIdentifier;
169
170private:
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.
186class Timer {
187public:
188 Timer() = default;
189 Timer(const Timer &other) = default;
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.
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.
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.
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.
236 Timer nest(TimingIdentifier name) {
237 return tm ? nest(id: name.getAsOpaquePointer(), nameBuilder: [=]() { return name.str(); })
238 : Timer();
239 }
240
241 /// See above.
242 Timer nest(StringRef name) {
243 return tm ? nest(name: TimingIdentifier::get(str: name, tm&: *tm)) : Timer();
244 }
245
246 /// Hide the timer in timing reports and directly show its children.
247 void hide() {
248 if (tm)
249 tm->hideTimer(handle);
250 }
251
252protected:
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
258private:
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.
272class TimingScope {
273public:
274 TimingScope() {}
275 TimingScope(const Timer &other) : timer(other) {
276 if (timer)
277 timer.start();
278 }
279 TimingScope(Timer &&other) : timer(std::move(other)) {
280 if (timer)
281 timer.start();
282 }
283 TimingScope(TimingScope &&other) : timer(std::move(other.timer)) {}
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.
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>
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.
316 void hide() { timer.hide(); }
317
318private:
319 /// The wrapped timer.
320 Timer timer;
321};
322
323//===----------------------------------------------------------------------===//
324// OutputStrategy
325//===----------------------------------------------------------------------===//
326
327/// Simple record class to record timing information.
328struct TimeRecord {
329 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.
351class OutputStrategy {
352public:
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.
388class DefaultTimingManager : public TimingManager {
389public:
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
453protected:
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
462private:
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.
470void registerDefaultTimingManagerCLOptions();
471
472/// Apply any values that were registered with
473/// 'registerDefaultTimingManagerOptions' to a `DefaultTimingManager`.
474void applyDefaultTimingManagerCLOptions(DefaultTimingManager &tm);
475
476} // namespace mlir
477
478#endif // MLIR_SUPPORT_TIMING_H
479

source code of mlir/include/mlir/Support/Timing.h