Line data Source code
1 : #include "timer.h"
2 : #include <iostream>
3 : #include <iomanip>
4 : #include <fstream>
5 : #include <thread>
6 :
7 : namespace hashbrowns {
8 :
9 39 : Timer::Timer(bool remove_outliers, double outlier_threshold)
10 78 : : last_duration_(0), is_running_(false),
11 117 : remove_outliers_(remove_outliers), outlier_threshold_(outlier_threshold) {
12 39 : }
13 :
14 115 : void Timer::start() {
15 115 : if (is_running_) {
16 1 : throw std::runtime_error("Timer is already running");
17 : }
18 114 : start_time_ = clock_type::now();
19 114 : is_running_ = true;
20 114 : }
21 :
22 115 : Timer::duration Timer::stop() {
23 115 : if (!is_running_) {
24 1 : throw std::runtime_error("Timer is not running");
25 : }
26 :
27 114 : auto end_time = clock_type::now();
28 114 : last_duration_ = std::chrono::duration_cast<duration>(end_time - start_time_);
29 114 : samples_.push_back(last_duration_);
30 114 : is_running_ = false;
31 :
32 114 : return last_duration_;
33 : }
34 :
35 14 : void Timer::add_sample(duration d) {
36 14 : samples_.push_back(d);
37 14 : }
38 :
39 3 : void Timer::reset() {
40 3 : samples_.clear();
41 3 : last_duration_ = duration(0);
42 3 : is_running_ = false;
43 3 : }
44 :
45 5 : Timer::Statistics Timer::get_statistics() const {
46 5 : if (samples_.empty()) {
47 1 : return {0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0};
48 : }
49 :
50 : // Work with a copy so we can sort without modifying original
51 4 : std::vector<duration> data = samples_;
52 :
53 : // Remove outliers if requested
54 4 : size_t original_count = data.size();
55 4 : if (remove_outliers_ && data.size() > 3) {
56 4 : data = remove_outliers(data);
57 : }
58 :
59 4 : if (data.empty()) {
60 0 : return {0.0, 0.0, 0.0, 0.0, 0.0, 0, 1.0}; // All samples were outliers
61 : }
62 :
63 : // Sort for median calculation
64 4 : std::sort(data.begin(), data.end());
65 :
66 : // Calculate basic statistics
67 4 : double sum = 0.0;
68 35 : for (const auto& d : data) {
69 31 : sum += d.count();
70 : }
71 :
72 4 : double mean = sum / data.size();
73 :
74 : // Median
75 : double median;
76 4 : size_t n = data.size();
77 4 : if (n % 2 == 0) {
78 1 : median = (data[n/2 - 1].count() + data[n/2].count()) / 2.0;
79 : } else {
80 3 : median = data[n/2].count();
81 : }
82 :
83 : // Standard deviation
84 4 : double variance_sum = 0.0;
85 35 : for (const auto& d : data) {
86 31 : double diff = d.count() - mean;
87 31 : variance_sum += diff * diff;
88 : }
89 4 : double std_dev = std::sqrt(variance_sum / data.size());
90 :
91 : // Min/max
92 4 : double min_val = data.front().count();
93 4 : double max_val = data.back().count();
94 :
95 : // Outlier ratio
96 4 : double outlier_ratio = static_cast<double>(original_count - data.size()) / original_count;
97 :
98 4 : return {mean, median, std_dev, min_val, max_val, data.size(), outlier_ratio};
99 4 : }
100 :
101 4 : std::vector<Timer::duration> Timer::remove_outliers(const std::vector<duration>& data) const {
102 4 : if (data.size() <= 3) {
103 0 : return data; // Too few samples for outlier detection
104 : }
105 :
106 : // Calculate mean and standard deviation
107 4 : double sum = 0.0;
108 38 : for (const auto& d : data) {
109 34 : sum += d.count();
110 : }
111 4 : double mean = sum / data.size();
112 :
113 4 : double variance_sum = 0.0;
114 38 : for (const auto& d : data) {
115 34 : double diff = d.count() - mean;
116 34 : variance_sum += diff * diff;
117 : }
118 4 : double std_dev = std::sqrt(variance_sum / data.size());
119 :
120 : // Filter out outliers
121 4 : std::vector<duration> filtered;
122 38 : for (const auto& d : data) {
123 34 : double zscore = calculate_zscore(d.count(), mean, std_dev);
124 34 : if (std::abs(zscore) <= outlier_threshold_) {
125 31 : filtered.push_back(d);
126 : }
127 : }
128 :
129 4 : return filtered.empty() ? data : filtered; // Return original if all were outliers
130 4 : }
131 :
132 34 : double Timer::calculate_zscore(double value, double mean, double std_dev) const {
133 34 : if (std_dev == 0.0) return 0.0;
134 34 : return (value - mean) / std_dev;
135 : }
136 :
137 : // ScopeTimer implementation
138 2 : ScopeTimer::ScopeTimer(const std::string& name, bool auto_print)
139 2 : : operation_name_(name), auto_print_(auto_print), stopped_(false) {
140 2 : start_time_ = Timer::clock_type::now();
141 2 : }
142 :
143 2 : ScopeTimer::~ScopeTimer() {
144 2 : if (!stopped_ && auto_print_) {
145 1 : auto duration = stop();
146 1 : std::cout << "[TIMER] ";
147 1 : if (!operation_name_.empty()) {
148 1 : std::cout << operation_name_ << ": ";
149 : }
150 1 : std::cout << std::fixed << std::setprecision(3)
151 1 : << duration.count() / 1000000.0 << " ms\n";
152 : }
153 2 : }
154 :
155 3 : Timer::duration ScopeTimer::elapsed() const {
156 3 : auto now = Timer::clock_type::now();
157 6 : return std::chrono::duration_cast<Timer::duration>(now - start_time_);
158 : }
159 :
160 3 : Timer::duration ScopeTimer::stop() {
161 3 : if (!stopped_) {
162 2 : stopped_ = true;
163 2 : return elapsed();
164 : }
165 1 : return Timer::duration(0);
166 : }
167 :
168 : // BenchmarkRunner implementation
169 1 : void BenchmarkRunner::print_comparison() const {
170 1 : if (results_.empty()) {
171 0 : std::cout << "No benchmark results to display.\n";
172 0 : return;
173 : }
174 :
175 1 : std::cout << "\n=== Benchmark Results ===\n";
176 1 : std::cout << std::left << std::setw(20) << "Benchmark"
177 1 : << std::right << std::setw(12) << "Mean (ms)"
178 : << std::setw(12) << "Median (ms)"
179 : << std::setw(12) << "Std Dev (ms)"
180 : << std::setw(15) << "Ops/Sec"
181 1 : << std::setw(10) << "Samples\n";
182 3 : std::cout << std::string(81, '-') << "\n";
183 :
184 3 : for (const auto& result : results_) {
185 2 : std::cout << std::left << std::setw(20) << result.name
186 2 : << std::right << std::fixed << std::setprecision(3)
187 2 : << std::setw(12) << result.stats.mean_ms()
188 2 : << std::setw(12) << result.stats.median_ms()
189 2 : << std::setw(12) << result.stats.std_dev_ms()
190 2 : << std::setw(15) << std::scientific << std::setprecision(2) << result.operations_per_second
191 2 : << std::setw(10) << result.stats.sample_count << "\n";
192 : }
193 :
194 : // Find fastest and slowest
195 1 : if (results_.size() > 1) {
196 1 : auto fastest = std::min_element(results_.begin(), results_.end(),
197 1 : [](const auto& a, const auto& b) { return a.stats.mean_ns < b.stats.mean_ns; });
198 1 : auto slowest = std::max_element(results_.begin(), results_.end(),
199 1 : [](const auto& a, const auto& b) { return a.stats.mean_ns < b.stats.mean_ns; });
200 :
201 1 : double speedup = slowest->stats.mean_ns / fastest->stats.mean_ns;
202 :
203 1 : std::cout << "\n--- Performance Analysis ---\n";
204 1 : std::cout << "Fastest: " << fastest->name << "\n";
205 1 : std::cout << "Slowest: " << slowest->name << "\n";
206 1 : std::cout << "Speedup: " << std::fixed << std::setprecision(2) << speedup << "x\n";
207 : }
208 :
209 1 : std::cout << "========================\n\n";
210 : }
211 :
212 1 : void BenchmarkRunner::export_csv(const std::string& filename) const {
213 1 : std::ofstream file(filename);
214 1 : if (!file) {
215 0 : throw std::runtime_error("Cannot open file for writing: " + filename);
216 : }
217 :
218 : // CSV header
219 1 : file << "Name,Mean_ns,Median_ns,StdDev_ns,Min_ns,Max_ns,Samples,Ops_per_sec,Data_size\n";
220 :
221 : // Data rows
222 3 : for (const auto& result : results_) {
223 2 : file << result.name << ","
224 2 : << result.stats.mean_ns << ","
225 2 : << result.stats.median_ns << ","
226 2 : << result.stats.std_dev_ns << ","
227 2 : << result.stats.min_ns << ","
228 2 : << result.stats.max_ns << ","
229 2 : << result.stats.sample_count << ","
230 2 : << result.operations_per_second << ","
231 2 : << result.data_size << "\n";
232 : }
233 :
234 1 : file.close();
235 1 : std::cout << "Benchmark results exported to: " << filename << "\n";
236 2 : }
237 :
238 : } // namespace hashbrowns
|