| //===- Timing.h - Execution time measurement facilities ---------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Facilities to measure and provide statistics on execution time. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef MLIR_SUPPORT_TIMING_H |
| #define MLIR_SUPPORT_TIMING_H |
| |
| #include "mlir/Support/LLVM.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/StringMapEntry.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| namespace mlir { |
| |
| class Timer; |
| class TimingManager; |
| class TimingScope; |
| class DefaultTimingManager; |
| namespace detail { |
| class TimingManagerImpl; |
| class DefaultTimingManagerImpl; |
| } // namespace detail |
| |
| //===----------------------------------------------------------------------===// |
| // TimingIdentifier |
| //===----------------------------------------------------------------------===// |
| |
| /// This class represesents a uniqued string owned by a `TimingManager`. Most |
| /// importantly, instances of this class provide a stable opaque pointer that |
| /// is guaranteed to be reproduced by later interning of the same string. The |
| /// `TimingManager` uses this mechanism to provide timers with an opaque id |
| /// even when the user of the API merely provided a string as identification |
| /// (instead of a pass for example). |
| /// |
| /// This is a POD type with pointer size, so it should be passed around by |
| /// value. The underlying data is owned by the `TimingManager`. |
| class TimingIdentifier { |
| using EntryType = llvm::StringMapEntry<llvm::NoneType>; |
| |
| public: |
| TimingIdentifier(const TimingIdentifier &) = default; |
| TimingIdentifier &operator=(const TimingIdentifier &other) = default; |
| |
| /// Return an identifier for the specified string. |
| static TimingIdentifier get(StringRef str, TimingManager &tm); |
| |
| /// Return a `StringRef` for the string. |
| StringRef strref() const { return entry->first(); } |
| |
| /// Return an `std::string`. |
| std::string str() const { return strref().str(); } |
| |
| /// Return the opaque pointer that corresponds to this identifier. |
| const void *getAsOpaquePointer() const { |
| return static_cast<const void *>(entry); |
| } |
| |
| private: |
| const EntryType *entry; |
| explicit TimingIdentifier(const EntryType *entry) : entry(entry) {} |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // TimingManager |
| //===----------------------------------------------------------------------===// |
| |
| /// This class represents facilities to measure execution time. |
| /// |
| /// Libraries and infrastructure code operate on opque `Timer` handles returned |
| /// by various functions of this manager. Timers are started and stopped to |
| /// demarcate regions in the code where execution time is of interest, and they |
| /// can be nested to provide more detailed timing resolution. Calls to the timer |
| /// start, stop, and nesting functions must be balanced. To facilitate this, |
| /// users are encouraged to leverage the `TimingScope` RAII-style wrapper around |
| /// `Timer`s. |
| /// |
| /// Users can provide their own implementation of `TimingManager`, or use the |
| /// default `DefaultTimingManager` implementation in MLIR. Implementations |
| /// override the various protected virtual functions to create, nest, start, and |
| /// stop timers. A common pattern is for subclasses to provide a custom timer |
| /// class and simply pass pointers to instances of this class around as the |
| /// opaque timer handle. The manager itself can then forward callbacks to the |
| /// this class. Alternatively, external timing libraries may return their own |
| /// opaque handles for timing scopes. |
| /// |
| /// For example: |
| /// ``` |
| /// void doWork(TimingManager &tm) { |
| /// auto root = tm.getRootScope(); |
| /// |
| /// { |
| /// auto scope = root.nest("First"); |
| /// doSomeWork(); |
| /// // <-- "First" timer stops here |
| /// } |
| /// |
| /// auto scope = root.nest("Second"); |
| /// doEvenMoreWork(); |
| /// scope.stop(); // <-- "Second" timer stops here |
| /// |
| /// // <-- Root timer stops here |
| /// } |
| /// ``` |
| class TimingManager { |
| public: |
| explicit TimingManager(); |
| virtual ~TimingManager(); |
| |
| /// Get the root timer of this timing manager. The returned timer must be |
| /// started and stopped manually. Execution time can be measured by nesting |
| /// timers within this root timer and starting/stopping them as appropriate. |
| /// Use this function only if you need access to the timer itself. Otherwise |
| /// consider the more convenient `getRootScope()` which offers an RAII-style |
| /// wrapper around the timer. |
| Timer getRootTimer(); |
| |
| /// Get the root timer of this timing manager wrapped in a `TimingScope` for |
| /// convenience. Automatically starts the timer and stops it as soon as the |
| /// `TimingScope` is destroyed, e.g. when it goes out of scope. |
| TimingScope getRootScope(); |
| |
| protected: |
| // Allow `Timer` access to the protected callbacks. |
| friend class Timer; |
| |
| //===--------------------------------------------------------------------===// |
| // Callbacks |
| // |
| // See the corresponding functions in `Timer` for additional details. |
| |
| /// Return the root timer. Implementations should return `llvm::None` if the |
| /// collection of timing samples is disabled. This will cause the timers |
| /// constructed from the manager to be tombstones which can be skipped |
| /// quickly. |
| virtual Optional<void *> rootTimer() = 0; |
| |
| /// Start the timer with the given handle. |
| virtual void startTimer(void *handle) = 0; |
| |
| /// Stop the timer with the given handle. |
| virtual void stopTimer(void *handle) = 0; |
| |
| /// Create a child timer nested within the one with the given handle. The `id` |
| /// parameter is used to uniquely identify the timer within its parent. |
| /// Multiple calls to this function with the same `handle` and `id` should |
| /// return the same timer, or at least cause the samples of the returned |
| /// timers to be combined for the final timing results. |
| virtual void *nestTimer(void *handle, const void *id, |
| function_ref<std::string()> nameBuilder) = 0; |
| |
| /// Hide the timer in timing reports and directly show its children. This is |
| /// merely a hint that implementations are free to ignore. |
| virtual void hideTimer(void *handle) {} |
| |
| protected: |
| const std::unique_ptr<detail::TimingManagerImpl> impl; |
| |
| // Allow `TimingIdentifier::get` access to the private impl details. |
| friend class TimingIdentifier; |
| |
| private: |
| // Disallow copying the manager. |
| TimingManager(const TimingManager &) = delete; |
| void operator=(const TimingManager &) = delete; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // Timer |
| //===----------------------------------------------------------------------===// |
| |
| /// A handle for a timer in a `TimingManager`. |
| /// |
| /// This class encapsulates a pointer to a `TimingManager` and an opaque handle |
| /// to a timer running within that manager. Libraries and infrastructure code |
| /// operate on `Timer` rather than any concrete classes handed out by custom |
| /// manager implementations. |
| class Timer { |
| public: |
| Timer() {} |
| Timer(const Timer &other) : tm(other.tm), handle(other.handle) {} |
| Timer(Timer &&other) : Timer(other) { |
| other.tm = nullptr; |
| other.handle = nullptr; |
| } |
| |
| Timer &operator=(Timer &&other) { |
| tm = other.tm; |
| handle = other.handle; |
| other.tm = nullptr; |
| other.handle = nullptr; |
| return *this; |
| } |
| |
| /// Returns whether this is a valid timer handle. Invalid timer handles are |
| /// used when timing is disabled in the `TimingManager` to keep the impact on |
| /// performance low. |
| explicit operator bool() const { return tm != nullptr; } |
| |
| /// Start the timer. This must be accompanied by a corresponding call to |
| /// `stop()` at a later point. |
| void start() { |
| if (tm) |
| tm->startTimer(handle); |
| } |
| |
| /// Stop the timer. This must have been preceded by a corresponding call to |
| /// `start()` at an earlier point. |
| void stop() { |
| if (tm) |
| tm->stopTimer(handle); |
| } |
| |
| /// Create a child timer nested within this one. Multiple calls to this |
| /// function with the same unique identifier `id` will return the same child |
| /// timer. The timer must have been started when calling this function. |
| /// |
| /// This function can be called from other threads, as long as this timer |
| /// is not stopped before any uses of the child timer on the other thread are |
| /// stopped. |
| /// |
| /// The `nameBuilder` function is not guaranteed to be called. |
| Timer nest(const void *id, function_ref<std::string()> nameBuilder) { |
| return tm ? Timer(*tm, tm->nestTimer(handle, id, std::move(nameBuilder))) |
| : Timer(); |
| } |
| |
| /// See above. |
| Timer nest(TimingIdentifier name) { |
| return tm ? nest(name.getAsOpaquePointer(), [=]() { return name.str(); }) |
| : Timer(); |
| } |
| |
| /// See above. |
| Timer nest(StringRef name) { |
| return tm ? nest(TimingIdentifier::get(name, *tm)) : Timer(); |
| } |
| |
| /// Hide the timer in timing reports and directly show its children. |
| void hide() { |
| if (tm) |
| tm->hideTimer(handle); |
| } |
| |
| protected: |
| Timer(TimingManager &tm, void *handle) : tm(&tm), handle(handle) {} |
| |
| // Allow the `TimingManager` access to the above constructor. |
| friend class TimingManager; |
| |
| private: |
| /// The associated timing manager. |
| TimingManager *tm = nullptr; |
| /// An opaque handle that identifies the timer in the timing manager |
| /// implementation. |
| void *handle = nullptr; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // TimingScope |
| //===----------------------------------------------------------------------===// |
| |
| /// An RAII-style wrapper around a timer that ensures the timer is properly |
| /// started and stopped. |
| class TimingScope { |
| public: |
| TimingScope() : timer() {} |
| TimingScope(const Timer &other) : timer(other) { |
| if (timer) |
| timer.start(); |
| } |
| TimingScope(Timer &&other) : timer(std::move(other)) { |
| if (timer) |
| timer.start(); |
| } |
| TimingScope(TimingScope &&other) : timer(std::move(other.timer)) {} |
| ~TimingScope() { stop(); } |
| |
| TimingScope &operator=(TimingScope &&other) { |
| stop(); |
| timer = std::move(other.timer); |
| return *this; |
| } |
| |
| /// Check if the timing scope actually contains a valid timer. |
| explicit operator bool() const { return bool(timer); } |
| |
| // Disable copying of the `TimingScope`. |
| TimingScope(const TimingScope &) = delete; |
| TimingScope &operator=(const TimingScope &) = delete; |
| |
| /// Manually stop the timer early. |
| void stop() { |
| timer.stop(); |
| timer = Timer(); |
| } |
| |
| /// Create a nested timing scope. |
| /// |
| /// This returns a new `TimingScope` with a timer nested within the current |
| /// scope. In this fashion, the time in this scope may be further subdivided |
| /// in a more fine-grained fashion. |
| template <typename... Args> |
| TimingScope nest(Args... args) { |
| return TimingScope(std::move(timer.nest(std::forward<Args>(args)...))); |
| } |
| |
| /// Hide the timer in timing reports and directly show its children. |
| void hide() { timer.hide(); } |
| |
| private: |
| /// The wrapped timer. |
| Timer timer; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // DefaultTimingManager |
| //===----------------------------------------------------------------------===// |
| |
| /// Facilities for time measurement and report printing to an output stream. |
| /// |
| /// This is MLIR's default implementation of a `TimingManager`. Prints an |
| /// execution time report upon destruction, or manually through `print()`. By |
| /// default the results are printed in `DisplayMode::Tree` mode to stderr. |
| /// Use `setEnabled(true)` to enable collection of timing samples; it is |
| /// disabled by default. |
| /// |
| /// You should only instantiate a `DefaultTimingManager` if you are writing a |
| /// tool and want to pass a timing manager to the remaining infrastructure. If |
| /// you are writing library or infrastructure code, you should rather accept |
| /// the `TimingManager` base class to allow for users of your code to substitute |
| /// their own timing implementations. Also, if you only intend to collect time |
| /// samples, consider accepting a `Timer` or `TimingScope` instead. |
| class DefaultTimingManager : public TimingManager { |
| public: |
| /// The different display modes for printing the timers. |
| enum class DisplayMode { |
| /// In this mode the results are displayed in a list sorted by total time, |
| /// with timers aggregated into one unique result per timer name. |
| List, |
| |
| /// In this mode the results are displayed in a tree view, with child timers |
| /// nested under their parents. |
| Tree, |
| }; |
| |
| DefaultTimingManager(); |
| DefaultTimingManager(DefaultTimingManager &&rhs); |
| virtual ~DefaultTimingManager(); |
| |
| // Disable copying of the `DefaultTimingManager`. |
| DefaultTimingManager(const DefaultTimingManager &rhs) = delete; |
| DefaultTimingManager &operator=(const DefaultTimingManager &rhs) = delete; |
| |
| /// Enable or disable execution time sampling. |
| void setEnabled(bool enabled); |
| |
| /// Return whether execution time sampling is enabled. |
| bool isEnabled() const; |
| |
| /// Change the display mode. |
| void setDisplayMode(DisplayMode displayMode); |
| |
| /// Return the current display mode; |
| DisplayMode getDisplayMode() const; |
| |
| /// Change the stream where the output will be printed to. |
| void setOutput(raw_ostream &os); |
| |
| /// Return the current output stream where the output will be printed to. |
| raw_ostream &getOutput() const; |
| |
| /// Print and clear the timing results. Only call this when there are no more |
| /// references to nested timers around, as printing post-processes and clears |
| /// the timers. |
| void print(); |
| |
| /// Clear the timing results. Only call this when there are no more references |
| /// to nested timers around, as clearing invalidates them. |
| void clear(); |
| |
| /// Debug print the timer data structures to an output stream. |
| void dumpTimers(raw_ostream &os = llvm::errs()); |
| |
| /// Debug print the timers as a list. Only call this when there are no more |
| /// references to nested timers around. |
| void dumpAsList(raw_ostream &os = llvm::errs()); |
| |
| /// Debug print the timers as a tree. Only call this when there are no |
| /// more references to nested timers around. |
| void dumpAsTree(raw_ostream &os = llvm::errs()); |
| |
| protected: |
| // `TimingManager` callbacks |
| Optional<void *> rootTimer() override; |
| void startTimer(void *handle) override; |
| void stopTimer(void *handle) override; |
| void *nestTimer(void *handle, const void *id, |
| function_ref<std::string()> nameBuilder) override; |
| void hideTimer(void *handle) override; |
| |
| private: |
| const std::unique_ptr<detail::DefaultTimingManagerImpl> impl; |
| }; |
| |
| /// Register a set of useful command-line options that can be used to configure |
| /// a `DefaultTimingManager`. The values of these options can be applied via the |
| /// `applyDefaultTimingManagerCLOptions` method. |
| void registerDefaultTimingManagerCLOptions(); |
| |
| /// Apply any values that were registered with |
| /// 'registerDefaultTimingManagerOptions' to a `DefaultTimingManager`. |
| void applyDefaultTimingManagerCLOptions(DefaultTimingManager &tm); |
| |
| } // namespace mlir |
| |
| #endif // MLIR_SUPPORT_TIMING_H |