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