Simple Runtime Analyzer
Loading...
Searching...
No Matches
runtime_analyzer.hpp
1
13#pragma once
14
15#include <chrono>
16#include <concepts>
17#include <cstddef>
18#include <functional>
19#include <ranges>
20#include <stdexcept>
21#include <string>
22#include <string_view>
23#include <type_traits>
24#include <utility>
25#include <vector>
26
27namespace sra
28{
29
30// clang-format off
31template <typename T>
32concept ChronoDuration = requires
33{
34 requires std::is_same_v<T, std::chrono::nanoseconds> ||
35 std::is_same_v<T, std::chrono::microseconds> ||
36 std::is_same_v<T, std::chrono::milliseconds> ||
37 std::is_same_v<T, std::chrono::seconds> ||
38 std::is_same_v<T, std::chrono::minutes> ||
39 std::is_same_v<T, std::chrono::hours>;
40};
41
42template <typename T>
43concept HasSize = requires(const T& t) { { t.size() } -> std::convertible_to<std::size_t>; };
44
45template <ChronoDuration T>
46inline constexpr std::string_view get_unit_symbol() noexcept
47{
48 if constexpr (std::is_same_v<T, std::chrono::nanoseconds>) { return "ns"; }
49 else if constexpr (std::is_same_v<T, std::chrono::microseconds>) { return "μs"; }
50 else if constexpr (std::is_same_v<T, std::chrono::milliseconds>) { return "ms"; }
51 else if constexpr (std::is_same_v<T, std::chrono::seconds>) { return "s"; }
52 else if constexpr (std::is_same_v<T, std::chrono::minutes>) { return "min"; }
53 else if constexpr (std::is_same_v<T, std::chrono::hours>) { return "h"; }
54 else { return "units"; }
55}
56// clang-format on
57
58template <ChronoDuration Unit>
60{
61 std::vector<Unit> raw_durations;
62 std::vector<size_t> sample_sizes;
63 std::string unit_symbol;
64
65 // Main constructor
66 RuntimeProfile(std::vector<Unit>&& _raw_durations, std::vector<size_t>&& _sample_sizes) noexcept
67 : raw_durations(std::move(_raw_durations))
68 , sample_sizes(std::move(_sample_sizes))
70 {}
71
72 // Conversion constructor
73 template <ChronoDuration OtherUnit>
76 {
77 raw_durations.reserve(other.raw_durations.size());
78 if constexpr (std::is_same_v<Unit, OtherUnit>) { raw_durations = other.raw_durations; }
79 else
80 {
81 for (const auto& t : other.raw_durations) { raw_durations.push_back(std::chrono::duration_cast<Unit>(t)); }
82 }
83 }
84
85 // Conversion method
86 template <ChronoDuration UnitTo>
87 [[nodiscard]] RuntimeProfile<UnitTo> convert_to() const
88 {
89 return RuntimeProfile<UnitTo>(*this);
90 }
91
92 // Utility methods
93 [[nodiscard]] std::size_t size() const noexcept { return raw_durations.size(); }
94 [[nodiscard]] bool empty() const noexcept { return raw_durations.empty(); }
95};
96
97template <ChronoDuration Unit = std::chrono::milliseconds, typename Func, typename... Args>
98requires std::invocable<Func, Args...>
99[[nodiscard]] auto measure_duration(Func&& func, Args&&... args)
100{
101 const auto start = std::chrono::high_resolution_clock::now();
102
103 if constexpr (std::is_void_v<std::invoke_result_t<Func, Args...>>)
104 {
105 std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
106 }
107 else
108 {
109 [[maybe_unused]] auto result = std::invoke(std::forward<Func>(func), std::forward<Args>(args)...);
110 }
111
112 const auto end = std::chrono::high_resolution_clock::now();
113 return std::chrono::duration_cast<Unit>(end - start);
114}
115
116template <ChronoDuration Unit = std::chrono::milliseconds,
117 typename Func,
118 std::ranges::range Container,
119 typename... Args>
120requires HasSize<std::ranges::range_value_t<Container>> &&
121 std::invocable<Func, const std::ranges::range_value_t<Container>&, Args...>
122[[nodiscard]] auto profile_runtime(Func&& func, const Container& samples, Args&&... args)
123{
124 if (std::ranges::empty(samples))
125 {
126 throw std::invalid_argument("Cannot profile runtime with empty samples container");
127 }
128
129 std::vector<Unit> raw_durations;
130 std::vector<size_t> sample_sizes;
131 const auto num_samples = std::ranges::size(samples);
132
133 raw_durations.reserve(num_samples);
134 sample_sizes.reserve(num_samples);
135
136 // Capture arguments safely
137 auto invoke_func = [&func, ... captured_args = std::forward<Args>(args)](const auto& sample) mutable {
138 std::invoke(func, sample, std::forward<Args>(captured_args)...);
139 };
140
141 for (const auto& sample : samples)
142 {
143 auto duration = measure_duration<Unit>([&invoke_func, &sample]() { invoke_func(sample); });
144
145 raw_durations.emplace_back(duration);
146 sample_sizes.emplace_back(sample.size());
147 }
148
149 return RuntimeProfile<Unit>{std::move(raw_durations), std::move(sample_sizes)};
150}
151
152template <ChronoDuration Unit>
153[[nodiscard]] inline auto calculate_average(const RuntimeProfile<Unit>& profile) noexcept
154{
155 if (profile.raw_durations.empty()) return Unit::zero();
156
157 typename Unit::rep total = 0;
158 for (const auto& duration : profile.raw_durations) { total += duration.count(); }
159 return Unit{total / profile.raw_durations.size()};
160}
161
162template <ChronoDuration Unit>
163[[nodiscard]] inline auto calculate_total(const RuntimeProfile<Unit>& profile) noexcept
164{
165 typename Unit::rep total = 0;
166 for (const auto& duration : profile.raw_durations) { total += duration.count(); }
167 return Unit{total};
168}
169
170} // namespace sra
Definition runtime_analyzer.hpp:32
Definition runtime_analyzer.hpp:43
Definition runtime_analyzer.hpp:28
constexpr std::string_view get_unit_symbol() noexcept
Definition runtime_analyzer.hpp:46
auto profile_runtime(Func &&func, const Container &samples, Args &&... args)
Definition runtime_analyzer.hpp:122
auto calculate_total(const RuntimeProfile< Unit > &profile) noexcept
Definition runtime_analyzer.hpp:163
auto calculate_average(const RuntimeProfile< Unit > &profile) noexcept
Definition runtime_analyzer.hpp:153
auto measure_duration(Func &&func, Args &&... args)
Definition runtime_analyzer.hpp:99
Definition runtime_analyzer.hpp:60
std::string unit_symbol
Definition runtime_analyzer.hpp:63
RuntimeProfile(const RuntimeProfile< OtherUnit > &other)
Definition runtime_analyzer.hpp:74
RuntimeProfile< UnitTo > convert_to() const
Definition runtime_analyzer.hpp:87
std::vector< Unit > raw_durations
Definition runtime_analyzer.hpp:61
RuntimeProfile(std::vector< Unit > &&_raw_durations, std::vector< size_t > &&_sample_sizes) noexcept
Definition runtime_analyzer.hpp:66
bool empty() const noexcept
Definition runtime_analyzer.hpp:94
std::size_t size() const noexcept
Definition runtime_analyzer.hpp:93
std::vector< size_t > sample_sizes
Definition runtime_analyzer.hpp:62