The Runtime Analyzer Module (runtime_analyzer.hpp
) is a set of utilities for C++ developers designed to measure and profile the performance of functions and algorithms. This tool provides high-precision timing capabilities, compatibility with multiple time units, and functions for statistical analysis of results. Its design is based on modern C++23 concepts and ranges to ensure type safety and flexibility.
Key Features
- Accurate Timing: Offers nanosecond to hour timing resolution using
std::chrono
.
- Concept-Based Design: Employs C++23 concepts to create type-safe interfaces.
- Flexible Units: Allows for automatic conversion between different time units.
- Statistical Profiling: Facilitates multi-sample profiling with the ability to track the size of each sample.
- Modern C++: Requires C++23, with support for concepts and ranges.
Concepts and Type Requirements
The module uses C++ concepts to enforce type safety and define clear requirements for templates.
ChronoDuration
: This concept limits template types to standard std::chrono
durations (e.g., nanoseconds
, microseconds
, milliseconds
).
HasSize
: This concept requires a container to have a size()
method that returns a type convertible to std::size_t
.
Core Components
RuntimeProfile Structure
The RuntimeProfile
struct is a templated container that stores timing results along with associated sample metadata, such as sample sizes. It is type-safe, supports conversion between time units, and uses move semantics for efficient handling of large data sets.
template <ChronoDuration Unit>
struct RuntimeProfile
{
std::vector<Unit> raw_durations;
std::vector<size_t> sample_sizes;
std::string unit_symbol;
RuntimeProfile(std::vector<Unit>&& _raw_durations, std::vector<size_t>&& _sample_sizes) noexcept
: raw_durations(std::move(_raw_durations))
, sample_sizes(std::move(_sample_sizes))
, unit_symbol(get_unit_symbol<Unit>())
{}
template <ChronoDuration OtherUnit>
explicit RuntimeProfile(const RuntimeProfile<OtherUnit>& other)
: sample_sizes(other.sample_sizes), unit_symbol(get_unit_symbol<Unit>())
{
raw_durations.reserve(other.raw_durations.size());
if constexpr (std::is_same_v<Unit, OtherUnit>) { raw_durations = other.raw_durations; }
else
{
for (const auto& t : other.raw_durations) { raw_durations.push_back(std::chrono::duration_cast<Unit>(t)); }
}
}
template <ChronoDuration UnitTo>
[[nodiscard]] RuntimeProfile<UnitTo> convert_to() const
{
return RuntimeProfile<UnitTo>(*this);
}
[[nodiscard]] std::size_t size() const noexcept { return raw_durations.size(); }
[[nodiscard]] bool empty() const noexcept { return raw_durations.empty(); }
};
measure_duration Function
The measure_duration
function measures the time it takes for a single function or expression to execute. It's compatible with both value-returning (non-void
) and non-value-returning (void
) functions, using perfect forwarding for arguments. The time unit can be specified at compile time.
template <ChronoDuration Unit = std::chrono::milliseconds, typename Func, typename... Args>
requires std::invocable<Func, Args...>
[[nodiscard]] auto measure_duration(Func&& func, Args&&... args)
{
const auto start = std::chrono::high_resolution_clock::now();
if constexpr (std::is_void_v<std::invoke_result_t<Func, Args...>>)
{
std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
}
else
{
[[maybe_unused]] auto result = std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
}
const auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<Unit>(end - start);
}
profile_runtime Function
The profile_runtime
function performs multi-sample profiling over a range of inputs. It requires that the samples container conforms to the HasSize
concept. The function captures and forwards arguments safely, returning an aggregated RuntimeProfile
object with all the results.
template <ChronoDuration Unit = std::chrono::milliseconds,
typename Func,
std::ranges::range Container,
typename... Args>
requires HasSize<std::ranges::range_value_t<Container>> &&
std::invocable<Func, const std::ranges::range_value_t<Container>&, Args...>
[[nodiscard]] auto profile_runtime(Func&& func, const Container& samples, Args&&... args)
{
if (std::ranges::empty(samples))
{
throw std::invalid_argument("Cannot profile runtime with empty samples container");
}
std::vector<Unit> raw_durations;
std::vector<size_t> sample_sizes;
const auto num_samples = std::ranges::size(samples);
raw_durations.reserve(num_samples);
sample_sizes.reserve(num_samples);
auto invoke_func = [&func, ... captured_args = std::forward<Args>(args)](const auto& sample) mutable {
std::invoke(func, sample, std::forward<Args>(captured_args)...);
};
for (const auto& sample : samples)
{
auto duration = measure_duration<Unit>([&invoke_func, &sample]() { invoke_func(sample); });
raw_durations.emplace_back(duration);
sample_sizes.emplace_back(sample.size());
}
return RuntimeProfile<Unit>{std::move(raw_durations), std::move(sample_sizes)};
}
Utility Functions
calculate_average
: Computes the arithmetic mean of the durations recorded in a RuntimeProfile
. It returns Unit::zero()
for empty profiles.
template <ChronoDuration Unit>
[[nodiscard]] inline auto calculate_average(const RuntimeProfile<Unit>& profile) noexcept
{
if (profile.raw_durations.empty()) return Unit::zero();
typename Unit::rep total = 0;
for (const auto& duration : profile.raw_durations) { total += duration.count(); }
return Unit{total / profile.raw_durations.size()};
}
calculate_total
: Computes the sum of all durations in a RuntimeProfile
.
template <ChronoDuration Unit>
[[nodiscard]] inline auto calculate_total(const RuntimeProfile<Unit>& profile) noexcept
{
typename Unit::rep total = 0;
for (const auto& duration : profile.raw_durations) { total += duration.count(); }
return Unit{total};
}
Technical Considerations
The module is designed with minimal overhead and robust error handling in mind. Type validations are enforced at compile-time by concepts, and the profile_runtime
function throws a std::invalid_argument
if an empty container is provided. The utility functions provide a no-throw guarantee (noexcept
).