Skip to content

Commit da312ec

Browse files
feat(core): add unit tests for statistics computation
1 parent ccded65 commit da312ec

File tree

5 files changed

+325
-112
lines changed

5 files changed

+325
-112
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.PHONY: format
2+
format:
3+
@echo "Formatting C++ files..."
4+
@git ls-files '*.cpp' '*.h' | grep -v '^google_benchmark/' | xargs -r clang-format -i
5+
@echo "Formatting complete!"

core/src/walltime.cpp

Lines changed: 81 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include "walltime.h"
2+
13
#include <algorithm>
24
#include <cassert>
35
#include <cmath>
@@ -20,44 +22,6 @@
2022

2123
namespace codspeed {
2224

23-
// Times are per iteration
24-
struct BenchmarkStats {
25-
double min_ns;
26-
double max_ns;
27-
double mean_ns;
28-
double stdev_ns;
29-
double q1_ns;
30-
double median_ns;
31-
double q3_ns;
32-
uint64_t rounds;
33-
double total_time;
34-
uint64_t iqr_outlier_rounds;
35-
uint64_t stdev_outlier_rounds;
36-
uint64_t iter_per_round;
37-
uint64_t warmup_iters;
38-
39-
BenchmarkStats(double min_ns = 0.0, double max_ns = 0.0, double mean_ns = 0.0,
40-
double stdev_ns = 0.0, double q1_ns = 0.0,
41-
double median_ns = 0.0, double q3_ns = 0.0,
42-
uint64_t rounds = 0, double total_time = 0.0,
43-
uint64_t iqr_outlier_rounds = 0,
44-
uint64_t stdev_outlier_rounds = 0, uint64_t iter_per_round = 0,
45-
uint64_t warmup_iters = 0)
46-
: min_ns(min_ns),
47-
max_ns(max_ns),
48-
mean_ns(mean_ns),
49-
stdev_ns(stdev_ns),
50-
q1_ns(q1_ns),
51-
median_ns(median_ns),
52-
q3_ns(q3_ns),
53-
rounds(rounds),
54-
total_time(total_time),
55-
iqr_outlier_rounds(iqr_outlier_rounds),
56-
stdev_outlier_rounds(stdev_outlier_rounds),
57-
iter_per_round(iter_per_round),
58-
warmup_iters(warmup_iters) {}
59-
};
60-
6125
struct BenchmarkMetadata {
6226
std::string name;
6327
std::string uri;
@@ -190,85 +154,90 @@ static void write_codspeed_benchmarks_to_json(
190154
}
191155
}
192156

157+
BenchmarkStats compute_benchmark_stats(
158+
const RawWalltimeBenchmark &raw_benchmark) {
159+
assert(raw_benchmark.iters_per_round.size() ==
160+
raw_benchmark.times_per_round_ns.size());
161+
162+
assert(raw_benchmark.iters_per_round.size() != 0);
163+
164+
// Convert total round times to per-iteration times
165+
std::vector<double> per_iteration_times_ns;
166+
for (size_t i = 0; i < raw_benchmark.times_per_round_ns.size(); i++) {
167+
assert(raw_benchmark.iters_per_round[i] != 0);
168+
double per_iter_time_ns =
169+
raw_benchmark.times_per_round_ns[i] / raw_benchmark.iters_per_round[i];
170+
per_iteration_times_ns.push_back(per_iter_time_ns);
171+
}
172+
173+
// Sort for quantile computation
174+
std::vector<double> sorted_per_iter_times_ns = per_iteration_times_ns;
175+
std::sort(sorted_per_iter_times_ns.begin(), sorted_per_iter_times_ns.end());
176+
177+
// Compute statistics from per-iteration times
178+
double mean_ns = std::accumulate(per_iteration_times_ns.begin(),
179+
per_iteration_times_ns.end(), 0.0) /
180+
per_iteration_times_ns.size();
181+
182+
double variance = 0.0;
183+
for (double time_ns : per_iteration_times_ns) {
184+
double diff = time_ns - mean_ns;
185+
variance += diff * diff;
186+
}
187+
double stdev_ns = std::sqrt(variance / per_iteration_times_ns.size());
188+
const double STDEV_OUTLIER_FACTOR = 3.0;
189+
size_t stdev_outlier_rounds = std::count_if(
190+
sorted_per_iter_times_ns.begin(), sorted_per_iter_times_ns.end(),
191+
[mean_ns, stdev_ns, STDEV_OUTLIER_FACTOR](double x) {
192+
return x < mean_ns - STDEV_OUTLIER_FACTOR * stdev_ns ||
193+
x > mean_ns + STDEV_OUTLIER_FACTOR * stdev_ns;
194+
});
195+
196+
double q1_ns = compute_quantile(sorted_per_iter_times_ns, 0.25);
197+
double median_ns = compute_quantile(sorted_per_iter_times_ns, 0.5);
198+
double q3_ns = compute_quantile(sorted_per_iter_times_ns, 0.75);
199+
200+
double iqr_ns = q3_ns - q1_ns;
201+
const double IQR_OUTLIER_FACTOR = 1.5;
202+
size_t iqr_outlier_rounds = std::count_if(
203+
sorted_per_iter_times_ns.begin(), sorted_per_iter_times_ns.end(),
204+
[q1_ns, q3_ns, iqr_ns, IQR_OUTLIER_FACTOR](double x) {
205+
return x < q1_ns - IQR_OUTLIER_FACTOR * iqr_ns ||
206+
x > q3_ns + IQR_OUTLIER_FACTOR * iqr_ns;
207+
});
208+
209+
// Compute total time in seconds
210+
double total_time =
211+
std::accumulate(raw_benchmark.times_per_round_ns.begin(),
212+
raw_benchmark.times_per_round_ns.end(), 0.0) /
213+
1e9;
214+
215+
// TODO: CodSpeed format only supports one iter_per_round for all rounds,
216+
// for now take the average
217+
uint64_t avg_iters_per_round =
218+
std::accumulate(raw_benchmark.iters_per_round.begin(),
219+
raw_benchmark.iters_per_round.end(), 0ULL) /
220+
raw_benchmark.iters_per_round.size();
221+
222+
// Populate stats
223+
return BenchmarkStats(*std::min_element(sorted_per_iter_times_ns.begin(),
224+
sorted_per_iter_times_ns.end()),
225+
*std::max_element(sorted_per_iter_times_ns.begin(),
226+
sorted_per_iter_times_ns.end()),
227+
mean_ns, stdev_ns, q1_ns, median_ns, q3_ns,
228+
raw_benchmark.times_per_round_ns.size(), total_time,
229+
iqr_outlier_rounds, stdev_outlier_rounds,
230+
avg_iters_per_round,
231+
0 // TODO: warmup_iters
232+
);
233+
}
234+
193235
void generate_codspeed_walltime_report(
194236
const std::vector<RawWalltimeBenchmark> &raw_walltime_benchmarks) {
195237
std::vector<CodspeedWalltimeBenchmark> codspeed_walltime_benchmarks;
196238

197239
for (const auto &raw_benchmark : raw_walltime_benchmarks) {
198-
assert(raw_benchmark.iters_per_round.size() ==
199-
raw_benchmark.times_per_round_ns.size());
200-
201-
assert(raw_benchmark.iters_per_round.size() != 0);
202-
203-
// Convert total round times to per-iteration times
204-
std::vector<double> per_iteration_times_ns;
205-
for (size_t i = 0; i < raw_benchmark.times_per_round_ns.size(); i++) {
206-
assert(raw_benchmark.iters_per_round[i] != 0);
207-
double per_iter_time_ns = raw_benchmark.times_per_round_ns[i] /
208-
raw_benchmark.iters_per_round[i];
209-
per_iteration_times_ns.push_back(per_iter_time_ns);
210-
}
211-
212-
// Sort for quantile computation
213-
std::vector<double> sorted_per_iter_times_ns = per_iteration_times_ns;
214-
std::sort(sorted_per_iter_times_ns.begin(), sorted_per_iter_times_ns.end());
215-
216-
// Compute statistics from per-iteration times
217-
double mean_ns = std::accumulate(per_iteration_times_ns.begin(),
218-
per_iteration_times_ns.end(), 0.0) /
219-
per_iteration_times_ns.size();
220-
221-
double variance = 0.0;
222-
for (double time_ns : per_iteration_times_ns) {
223-
double diff = time_ns - mean_ns;
224-
variance += diff * diff;
225-
}
226-
double stdev_ns = std::sqrt(variance / per_iteration_times_ns.size());
227-
const double STDEV_OUTLIER_FACTOR = 3.0;
228-
size_t stdev_outlier_rounds = std::count_if(
229-
sorted_per_iter_times_ns.begin(), sorted_per_iter_times_ns.end(),
230-
[mean_ns, stdev_ns, STDEV_OUTLIER_FACTOR](double x) {
231-
return x < mean_ns - STDEV_OUTLIER_FACTOR * stdev_ns ||
232-
x > mean_ns + STDEV_OUTLIER_FACTOR * stdev_ns;
233-
});
234-
235-
double q1_ns = compute_quantile(sorted_per_iter_times_ns, 0.25);
236-
double median_ns = compute_quantile(sorted_per_iter_times_ns, 0.5);
237-
double q3_ns = compute_quantile(sorted_per_iter_times_ns, 0.75);
238-
239-
double iqr_ns = q3_ns - q1_ns;
240-
const double IQR_OUTLIER_FACTOR = 1.5;
241-
size_t iqr_outlier_rounds = std::count_if(
242-
sorted_per_iter_times_ns.begin(), sorted_per_iter_times_ns.end(),
243-
[q1_ns, q3_ns, iqr_ns, IQR_OUTLIER_FACTOR](double x) {
244-
return x < q1_ns - IQR_OUTLIER_FACTOR * iqr_ns ||
245-
x > q3_ns + IQR_OUTLIER_FACTOR * iqr_ns;
246-
});
247-
248-
// Compute total time in seconds
249-
double total_time =
250-
std::accumulate(raw_benchmark.times_per_round_ns.begin(),
251-
raw_benchmark.times_per_round_ns.end(), 0.0) /
252-
1e9;
253-
254-
// TODO: CodSpeed format only supports one iter_per_round for all rounds,
255-
// for now take the average
256-
uint64_t avg_iters_per_round =
257-
std::accumulate(raw_benchmark.iters_per_round.begin(),
258-
raw_benchmark.iters_per_round.end(), 0ULL) /
259-
raw_benchmark.iters_per_round.size();
260-
261-
// Populate stats
262-
BenchmarkStats stats(*std::min_element(sorted_per_iter_times_ns.begin(),
263-
sorted_per_iter_times_ns.end()),
264-
*std::max_element(sorted_per_iter_times_ns.begin(),
265-
sorted_per_iter_times_ns.end()),
266-
mean_ns, stdev_ns, q1_ns, median_ns, q3_ns,
267-
raw_benchmark.times_per_round_ns.size(), total_time,
268-
iqr_outlier_rounds, stdev_outlier_rounds,
269-
avg_iters_per_round,
270-
0 // TODO: warmup_iters
271-
);
240+
BenchmarkStats stats = compute_benchmark_stats(raw_benchmark);
272241
CodspeedWalltimeBenchmark codspeed_benchmark;
273242
codspeed_benchmark.metadata = {raw_benchmark.name, raw_benchmark.uri};
274243
codspeed_benchmark.stats = stats;

core/src/walltime.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#ifndef CODSPEED_WALLTIME_INTERNAL_H
2+
#define CODSPEED_WALLTIME_INTERNAL_H
3+
4+
#include <cstdint>
5+
#include <vector>
6+
7+
#include "codspeed.h"
8+
9+
namespace codspeed {
10+
11+
// Times are per iteration
12+
struct BenchmarkStats {
13+
double min_ns;
14+
double max_ns;
15+
double mean_ns;
16+
double stdev_ns;
17+
double q1_ns;
18+
double median_ns;
19+
double q3_ns;
20+
uint64_t rounds;
21+
double total_time;
22+
uint64_t iqr_outlier_rounds;
23+
uint64_t stdev_outlier_rounds;
24+
uint64_t iter_per_round;
25+
uint64_t warmup_iters;
26+
27+
BenchmarkStats(double min_ns = 0.0, double max_ns = 0.0, double mean_ns = 0.0,
28+
double stdev_ns = 0.0, double q1_ns = 0.0,
29+
double median_ns = 0.0, double q3_ns = 0.0,
30+
uint64_t rounds = 0, double total_time = 0.0,
31+
uint64_t iqr_outlier_rounds = 0,
32+
uint64_t stdev_outlier_rounds = 0, uint64_t iter_per_round = 0,
33+
uint64_t warmup_iters = 0)
34+
: min_ns(min_ns),
35+
max_ns(max_ns),
36+
mean_ns(mean_ns),
37+
stdev_ns(stdev_ns),
38+
q1_ns(q1_ns),
39+
median_ns(median_ns),
40+
q3_ns(q3_ns),
41+
rounds(rounds),
42+
total_time(total_time),
43+
iqr_outlier_rounds(iqr_outlier_rounds),
44+
stdev_outlier_rounds(stdev_outlier_rounds),
45+
iter_per_round(iter_per_round),
46+
warmup_iters(warmup_iters) {}
47+
};
48+
49+
// Compute benchmark statistics from raw walltime data
50+
BenchmarkStats compute_benchmark_stats(
51+
const RawWalltimeBenchmark &raw_benchmark);
52+
53+
} // namespace codspeed
54+
55+
#endif // CODSPEED_WALLTIME_INTERNAL_H

core/test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ FetchContent_MakeAvailable(googletest)
99
add_executable(unit_tests
1010
uri.cpp
1111
codspeed.cpp
12+
walltime.cpp
1213
)
1314

15+
target_include_directories(unit_tests PRIVATE ${CMAKE_SOURCE_DIR}/src)
16+
1417
target_link_libraries(unit_tests
1518
PRIVATE
1619
codspeed

0 commit comments

Comments
 (0)