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. |
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 | |
67 | private: |
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 | /// ``` |
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. |
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; |
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 | |
252 | protected: |
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: |
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 | |
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 { |
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. |
351 | class OutputStrategy { |
352 | public: |
353 | OutputStrategy(raw_ostream &os) : os(os) {} |
354 | virtual ~OutputStrategy() = default; |
355 | |
356 | virtual void (const TimeRecord &total) = 0; |
357 | virtual void () = 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 | |