//===- 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" #include 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; 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(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 `std::nullopt` 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 std::optional 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 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 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() = default; Timer(const Timer &other) = default; 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 nameBuilder) { return tm ? Timer(*tm, tm->nestTimer(handle, id, 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() {} 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 TimingScope nest(Args... args) { return TimingScope(std::move(timer.nest(std::forward(args)...))); } /// Hide the timer in timing reports and directly show its children. void hide() { timer.hide(); } private: /// The wrapped timer. Timer timer; }; //===----------------------------------------------------------------------===// // OutputStrategy //===----------------------------------------------------------------------===// /// Simple record class to record timing information. struct TimeRecord { TimeRecord(double wall = 0.0, double user = 0.0) : wall(wall), user(user) {} TimeRecord &operator+=(const TimeRecord &other) { wall += other.wall; user += other.user; return *this; } TimeRecord &operator-=(const TimeRecord &other) { wall -= other.wall; user -= other.user; return *this; } double wall, user; }; /// Facilities for printing timing reports to various output formats. /// /// This is an abstract class that serves as the foundation for printing. /// Users can implement additional output formats by extending this abstract /// class. class OutputStrategy { public: OutputStrategy(raw_ostream &os) : os(os) {} virtual ~OutputStrategy() = default; virtual void printHeader(const TimeRecord &total) = 0; virtual void printFooter() = 0; virtual void printTime(const TimeRecord &time, const TimeRecord &total) = 0; virtual void printListEntry(StringRef name, const TimeRecord &time, const TimeRecord &total, bool lastEntry = false) = 0; virtual void printTreeEntry(unsigned indent, StringRef name, const TimeRecord &time, const TimeRecord &total) = 0; virtual void printTreeEntryEnd(unsigned indent, bool lastEntry = false) = 0; raw_ostream &os; }; //===----------------------------------------------------------------------===// // 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, }; /// The different output formats for printing the timers. enum class OutputFormat { /// In this format the results are displayed in text format. Text, /// In this format the results are displayed in JSON format. Json, }; DefaultTimingManager(); DefaultTimingManager(DefaultTimingManager &&rhs); ~DefaultTimingManager() override; // 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(std::unique_ptr output); /// 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 std::optional rootTimer() override; void startTimer(void *handle) override; void stopTimer(void *handle) override; void *nestTimer(void *handle, const void *id, function_ref nameBuilder) override; void hideTimer(void *handle) override; private: const std::unique_ptr impl; std::unique_ptr out; }; /// 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); /// Create an output strategy for the specified format, to be passed to /// DefaultTimingManager::setOutput(). std::unique_ptr createOutputStrategy(DefaultTimingManager::OutputFormat fmt, raw_ostream &os); } // namespace mlir #endif // MLIR_SUPPORT_TIMING_H