Skip to content

Commit fa18cdf

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

File tree

4 files changed

+319
-112
lines changed

4 files changed

+319
-112
lines changed

core/src/walltime.cpp

Lines changed: 80 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "codspeed.h"
1313
#include "utils.h"
14+
#include "walltime_internal.h"
1415
#ifdef _WIN32
1516
#include <process.h>
1617
#else
@@ -20,44 +21,6 @@
2021

2122
namespace codspeed {
2223

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-
6124
struct BenchmarkMetadata {
6225
std::string name;
6326
std::string uri;
@@ -190,85 +153,90 @@ static void write_codspeed_benchmarks_to_json(
190153
}
191154
}
192155

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

197238
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-
);
239+
BenchmarkStats stats = compute_benchmark_stats(raw_benchmark);
272240
CodspeedWalltimeBenchmark codspeed_benchmark;
273241
codspeed_benchmark.metadata = {raw_benchmark.name, raw_benchmark.uri};
274242
codspeed_benchmark.stats = stats;

core/src/walltime_internal.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)