blob: 89f3e01632ab809dbcd032a6d1b97dcb43aeb256 [file] [log] [blame]
//===- 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