miniacc.hh
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <cstdlib>
6#include <functional>
7#include <iomanip>
8#include <iostream>
9#include <fstream>
10#include <limits>
11#include <sstream>
12#include <string>
13#include <unordered_map>
14#include <utility>
15#include <vector>
16
17/// @file
18/// @brief miniacc: a header-only accuracy-benchmark harness (register cases, sample, summarise, CSV).
19///
20/// Registered with MINI_ACC_BENCHMARK_REGISTER_SIZES; a program built with MINI_ACC_MAIN()
21/// parses `--samples`, `--seed`, `--log10-cond`, `--csv`, `--benchmark_filter`,
22/// `--backend`, `--type` and positional argument lists. Installed because it is
23/// under include/batchlas; not a stable interface.
24/// @ingroup api_internal_helpers
25
26// Helpers to generate unique variable names in macros.
27#define MINI_ACC_CONCAT_INNER(x, y) x##y
28#define MINI_ACC_CONCAT(x, y) MINI_ACC_CONCAT_INNER(x, y)
29#define MINI_ACC_UNIQUE_NAME(prefix) MINI_ACC_CONCAT(prefix, __COUNTER__)
30
31// Register a benchmark and apply a sizing function at static-init time.
32#define MINI_ACC_BENCHMARK_REGISTER_SIZES(FUNC, SIZER) \
33 static int MINI_ACC_UNIQUE_NAME(_reg_sizes_) = ([]() { \
34 SIZER(miniacc::RegisterBenchmark(#FUNC, FUNC)); \
35 return 0; \
36 })();
37
38namespace miniacc {
39
40constexpr const char* kColorReset = "\033[0m";
41constexpr const char* kColorHeader = "\033[1;36m";
42constexpr const char* kColorName = "\033[1m";
43
44/// @brief Run-wide settings parsed from the command line.
45/// @ingroup api_internal_helpers
46struct Config {
47 size_t samples = 1024;
48 unsigned int seed = 1234u;
49 std::vector<double> log10_cond_values;
50};
51
52struct SampleRow {
53 size_t sample_index = 0;
54 bool ok = true;
55 int info_code = 0;
56 std::string failure_reason;
57 std::unordered_map<std::string, double> metrics;
58 std::unordered_map<std::string, std::string> tags;
59};
60
62 size_t count = 0;
63 size_t finite_count = 0;
64 size_t nan_count = 0;
65 double mean = std::numeric_limits<double>::quiet_NaN();
66 double p95 = std::numeric_limits<double>::quiet_NaN();
67 double max = std::numeric_limits<double>::quiet_NaN();
68};
69
70struct Result {
71 std::string name;
72 std::vector<double> args;
73 double target_log10_cond = std::numeric_limits<double>::quiet_NaN();
74 size_t sample_count = 0;
75 size_t failure_count = 0;
76 double failure_rate = 0.0;
77 std::unordered_map<std::string, MetricSummary> summary;
78 std::vector<SampleRow> sample_rows;
79};
80
81inline std::vector<double> parse_range_or_list_double(const std::string& str) {
82 std::vector<double> vals;
83 if (str.empty()) return vals;
84
85 if (str.find(':') != std::string::npos) {
86 std::vector<std::string> parts;
87 std::stringstream ss(str);
88 std::string item;
89 while (std::getline(ss, item, ':')) parts.push_back(item);
90 if (parts.size() >= 2) {
91 const double start = std::stod(parts[0]);
92 const double end = std::stod(parts[1]);
93 size_t num = parts.size() >= 3 ? std::stoul(parts[2]) : 5;
94 if (num < 2) num = 2;
95 for (size_t i = 0; i < num; ++i) {
96 const double t = static_cast<double>(i) / static_cast<double>(num - 1);
97 vals.push_back(start + (end - start) * t);
98 }
99 }
100 return vals;
101 }
102
103 if (str.find(',') != std::string::npos) {
104 std::stringstream ss(str);
105 std::string item;
106 while (std::getline(ss, item, ',')) {
107 if (!item.empty()) vals.push_back(std::stod(item));
108 }
109 return vals;
110 }
111
112 vals.push_back(std::stod(str));
113 return vals;
114}
115
116inline std::vector<std::string> parse_list(const std::string& str) {
117 std::vector<std::string> vals;
118 std::stringstream ss(str);
119 std::string item;
120 while (std::getline(ss, item, ',')) {
121 if (!item.empty()) vals.push_back(item);
122 }
123 return vals;
124}
125
126inline std::vector<double> dedup_nearly_equal(const std::vector<double>& input,
127 double eps = 1e-12) {
128 std::vector<double> out;
129 out.reserve(input.size());
130 for (double v : input) {
131 bool seen = false;
132 for (double u : out) {
133 if (std::abs(u - v) <= eps) {
134 seen = true;
135 break;
136 }
137 }
138 if (!seen) out.push_back(v);
139 }
140 return out;
141}
142
143/// @brief Per-case context handed to a benchmark: its arguments, the run settings, and the sample sink.
144/// @ingroup api_internal_helpers
145class State {
146public:
147 State(std::vector<double> args,
148 Config cfg,
149 double target_log10_cond,
150 std::string benchmark_name)
151 : args_(std::move(args)),
152 cfg_(std::move(cfg)),
153 target_log10_cond_(target_log10_cond),
154 benchmark_name_(std::move(benchmark_name)) {}
155
156 double arg(size_t idx) const {
157 return idx < args_.size() ? args_[idx] : 0.0;
158 }
159
160 int arg_int(size_t idx) const {
161 return static_cast<int>(std::lround(arg(idx)));
162 }
163
164 size_t samples() const { return cfg_.samples; }
165 unsigned int seed() const { return cfg_.seed; }
166 double target_log10_cond() const { return target_log10_cond_; }
167 const std::string& benchmark_name() const { return benchmark_name_; }
168
169 void SetTag(const std::string& key, const std::string& value) {
170 case_tags_[key] = value;
171 }
172
173 void RecordSample(const std::unordered_map<std::string, double>& metrics,
174 bool ok = true,
175 const std::string& failure_reason = "",
176 int info_code = 0,
177 const std::unordered_map<std::string, std::string>& tags = {}) {
178 SampleRow row;
179 row.sample_index = rows_.size();
180 row.ok = ok;
181 row.info_code = info_code;
182 row.failure_reason = failure_reason;
183 row.metrics = metrics;
184 row.tags = case_tags_;
185 for (const auto& kv : tags) {
186 row.tags[kv.first] = kv.second;
187 }
188 rows_.push_back(std::move(row));
189 }
190
191 const std::vector<SampleRow>& rows() const { return rows_; }
192
193private:
194 std::vector<double> args_;
195 Config cfg_;
196 double target_log10_cond_;
197 std::string benchmark_name_;
198 std::unordered_map<std::string, std::string> case_tags_;
199 std::vector<SampleRow> rows_;
200};
201
202using BenchFunc = std::function<void(State&)>;
203
204/// @brief A registered case: name, function, and its argument lists (Args, ArgRange, ArgsProduct).
205/// @ingroup api_internal_helpers
206struct Benchmark {
207 std::string name;
209 std::vector<std::vector<double>> args_list;
210
211 Benchmark(const std::string& n, BenchFunc f) : name(n), func(std::move(f)) {}
212
213 Benchmark* Args(const std::vector<double>& a) {
214 args_list.push_back(a);
215 return this;
216 }
217
218 Benchmark* ArgRange(double start, double end, size_t num = 5) {
219 if (num < 2) num = 2;
220 for (size_t i = 0; i < num; ++i) {
221 const double t = static_cast<double>(i) / static_cast<double>(num - 1);
222 args_list.push_back({start + (end - start) * t});
223 }
224 return this;
225 }
226
227 Benchmark* ArgsProduct(const std::vector<std::vector<double>>& ranges) {
228 std::vector<std::vector<double>> combos{{}};
229 for (const auto& r : ranges) {
230 std::vector<std::vector<double>> next;
231 for (const auto& c : combos) {
232 for (double v : r) {
233 auto tmp = c;
234 tmp.push_back(v);
235 next.push_back(std::move(tmp));
236 }
237 }
238 combos = std::move(next);
239 }
240 for (auto& c : combos) args_list.push_back(std::move(c));
241 return this;
242 }
243};
244
245inline std::vector<Benchmark>& registry() {
246 static std::vector<Benchmark> bench_list;
247 return bench_list;
248}
249
250inline Benchmark* RegisterBenchmark(const std::string& name, BenchFunc func) {
251 registry().emplace_back(name, std::move(func));
252 return &registry().back();
253}
254
255inline MetricSummary summarize_metric(const std::vector<SampleRow>& rows,
256 const std::string& metric_name) {
257 MetricSummary out;
258 out.count = rows.size();
259
260 std::vector<double> finite_values;
261 finite_values.reserve(rows.size());
262
263 for (const auto& r : rows) {
264 auto it = r.metrics.find(metric_name);
265 if (it == r.metrics.end() || !std::isfinite(it->second)) {
266 ++out.nan_count;
267 continue;
268 }
269 finite_values.push_back(it->second);
270 }
271
272 out.finite_count = finite_values.size();
273 if (finite_values.empty()) return out;
274
275 std::sort(finite_values.begin(), finite_values.end());
276 double sum = 0.0;
277 for (double v : finite_values) sum += v;
278 out.mean = sum / static_cast<double>(finite_values.size());
279 out.max = finite_values.back();
280
281 const size_t n = finite_values.size();
282 const size_t p95_idx = (n == 1) ? 0 : static_cast<size_t>(std::ceil(0.95 * static_cast<double>(n - 1)));
283 out.p95 = finite_values[p95_idx];
284
285 return out;
286}
287
289 const std::vector<double>& args,
290 const Config& cfg,
291 double target_log10_cond,
292 bool keep_samples) {
293 State state(args, cfg, target_log10_cond, b.name);
294 b.func(state);
295
296 Result res;
297 res.name = b.name;
298 res.args = args;
299 res.target_log10_cond = target_log10_cond;
300 res.sample_count = state.rows().size();
301
302 for (const auto& row : state.rows()) {
303 if (!row.ok) ++res.failure_count;
304 }
305 if (res.sample_count > 0) {
306 res.failure_rate = static_cast<double>(res.failure_count) / static_cast<double>(res.sample_count);
307 }
308
309 std::unordered_map<std::string, bool> seen_metrics;
310 std::vector<std::string> metric_names;
311 for (const auto& row : state.rows()) {
312 for (const auto& kv : row.metrics) {
313 if (!seen_metrics[kv.first]) {
314 metric_names.push_back(kv.first);
315 seen_metrics[kv.first] = true;
316 }
317 }
318 }
319 for (const auto& m : metric_names) {
320 res.summary[m] = summarize_metric(state.rows(), m);
321 }
322
323 if (keep_samples) {
324 res.sample_rows = state.rows();
325 }
326
327 return res;
328}
329
330inline bool match_filter(const Benchmark& b,
331 const std::string& benchmark_filter,
332 const std::vector<std::string>& backends,
333 const std::vector<std::string>& types) {
334 if (!benchmark_filter.empty()) {
335 const auto glob_match = [](const std::string& pattern, const std::string& text) {
336 size_t p = 0;
337 size_t t = 0;
338 size_t star = std::string::npos;
339 size_t match = 0;
340 while (t < text.size()) {
341 if (p < pattern.size() && (pattern[p] == '?' || pattern[p] == text[t])) {
342 ++p;
343 ++t;
344 } else if (p < pattern.size() && pattern[p] == '*') {
345 star = p++;
346 match = t;
347 } else if (star != std::string::npos) {
348 p = star + 1;
349 t = ++match;
350 } else {
351 return false;
352 }
353 }
354 while (p < pattern.size() && pattern[p] == '*') ++p;
355 return p == pattern.size();
356 };
357
358 if (!glob_match(benchmark_filter, b.name)) return false;
359 }
360
361 if (!backends.empty()) {
362 bool ok = false;
363 for (const auto& be : backends) {
364 if (b.name.find("Backend::" + be) != std::string::npos) {
365 ok = true;
366 break;
367 }
368 }
369 if (!ok) return false;
370 }
371
372 if (!types.empty()) {
373 bool ok = false;
374 for (const auto& t : types) {
375 if (t == "float" &&
376 b.name.find("<float") != std::string::npos &&
377 b.name.find("complex<float>") == std::string::npos)
378 ok = true;
379 else if (t == "double" &&
380 b.name.find("<double") != std::string::npos &&
381 b.name.find("complex<double>") == std::string::npos)
382 ok = true;
383 else if ((t == "cfloat" || t == "complex<float>") &&
384 b.name.find("complex<float>") != std::string::npos)
385 ok = true;
386 else if ((t == "cdouble" || t == "complex<double>") &&
387 b.name.find("complex<double>") != std::string::npos)
388 ok = true;
389 if (ok) break;
390 }
391 if (!ok) return false;
392 }
393
394 return true;
395}
396
397inline std::string format_value(double v) {
398 if (!std::isfinite(v)) return "";
399 std::ostringstream oss;
400 oss << std::setprecision(6) << std::showpoint << v;
401 return oss.str();
402}
403
404inline double metric_mean_or_nan(const Result& r,
405 const std::initializer_list<const char*>& keys) {
406 for (const char* k : keys) {
407 auto it = r.summary.find(k);
408 if (it != r.summary.end() && std::isfinite(it->second.mean)) {
409 return it->second.mean;
410 }
411 }
412 return std::numeric_limits<double>::quiet_NaN();
413}
414
416 bool show_r = false;
417 bool show_o = false;
418 bool show_relerr = false;
423 bool show_fill_ratio = false;
424 bool show_iter_ratio = false;
425 bool show_time_ratio = false;
427};
428
429inline bool has_any_metric(const Result& r,
430 const std::initializer_list<const char*>& keys) {
431 return std::isfinite(metric_mean_or_nan(r, keys));
432}
433
434inline SummaryMetricColumns detect_summary_columns(const std::vector<Result>& results) {
436 for (const auto& r : results) {
437 cols.show_r = cols.show_r || has_any_metric(r, {"R"});
438 cols.show_o = cols.show_o || has_any_metric(r, {"O", "orthogonality"});
439 cols.show_relerr = cols.show_relerr || has_any_metric(r, {"max_relerr", "relerr"});
440 cols.show_iterations_done = cols.show_iterations_done || has_any_metric(r, {"iterations_done"});
441 cols.show_total_time_sec = cols.show_total_time_sec || has_any_metric(r, {"total_time_sec", "solve_time_sec"});
442 cols.show_final_best_max = cols.show_final_best_max || has_any_metric(r, {"final_best_max", "final_best_mean"});
443 cols.show_converged_fraction = cols.show_converged_fraction || has_any_metric(r, {"converged_fraction"});
444 cols.show_fill_ratio = cols.show_fill_ratio || has_any_metric(r, {"fill_ratio"});
445 cols.show_iter_ratio = cols.show_iter_ratio || has_any_metric(r, {"iter_ratio_vs_baseline"});
446 cols.show_time_ratio = cols.show_time_ratio || has_any_metric(r, {"time_ratio_vs_baseline"});
447 cols.show_residual_ratio = cols.show_residual_ratio || has_any_metric(r, {"residual_ratio_vs_baseline"});
448 }
449 return cols;
450}
451
452inline void print_summary_header(size_t name_width,
453 size_t max_args,
454 const SummaryMetricColumns& cols) {
455 std::cout << kColorHeader
456 << std::left << std::setw(static_cast<int>(name_width)) << "Name";
457 for (size_t i = 0; i < max_args; ++i) {
458 std::ostringstream col;
459 col << "Arg" << i;
460 std::cout << std::setw(10) << col.str();
461 }
462 std::cout << std::setw(12) << "log10cond"
463 << std::setw(10) << "Samples"
464 << std::setw(10) << "Fail%";
465 if (cols.show_r) std::cout << std::setw(28) << "||AZ - Z\\Lambda||_F / n";
466 if (cols.show_o) std::cout << std::setw(24) << "||Z^T Z - I||_F / n";
467 if (cols.show_relerr) std::cout << std::setw(16) << "max_relerr";
468 if (cols.show_iterations_done) std::cout << std::setw(14) << "iters";
469 if (cols.show_total_time_sec) std::cout << std::setw(14) << "time_s";
470 if (cols.show_final_best_max) std::cout << std::setw(14) << "final_res";
471 if (cols.show_converged_fraction) std::cout << std::setw(14) << "conv_frac";
472 if (cols.show_fill_ratio) std::cout << std::setw(14) << "fill_ratio";
473 if (cols.show_iter_ratio) std::cout << std::setw(14) << "iter_ratio";
474 if (cols.show_time_ratio) std::cout << std::setw(14) << "time_ratio";
475 if (cols.show_residual_ratio) std::cout << std::setw(14) << "resid_ratio";
476 std::cout << kColorReset << '\n';
477}
478
479inline void print_summary_row(const Result& r,
480 size_t name_width,
481 size_t max_args,
482 const SummaryMetricColumns& cols) {
483 const auto arg_at = [&](size_t i) -> double {
484 return i < r.args.size() ? r.args[i] : std::numeric_limits<double>::quiet_NaN();
485 };
486
487 const double avg_log10_cond = metric_mean_or_nan(r, {"log10_cond"});
488 const double mean_r = metric_mean_or_nan(r, {"R"});
489 const double mean_o = metric_mean_or_nan(r, {"O", "orthogonality"});
490 const double mean_relerr = metric_mean_or_nan(r, {"max_relerr", "relerr"});
491 const double mean_iterations_done = metric_mean_or_nan(r, {"iterations_done"});
492 const double mean_total_time_sec = metric_mean_or_nan(r, {"total_time_sec", "solve_time_sec"});
493 const double mean_final_best_max = metric_mean_or_nan(r, {"final_best_max", "final_best_mean"});
494 const double mean_converged_fraction = metric_mean_or_nan(r, {"converged_fraction"});
495 const double mean_fill_ratio = metric_mean_or_nan(r, {"fill_ratio"});
496 const double mean_iter_ratio = metric_mean_or_nan(r, {"iter_ratio_vs_baseline"});
497 const double mean_time_ratio = metric_mean_or_nan(r, {"time_ratio_vs_baseline"});
498 const double mean_residual_ratio = metric_mean_or_nan(r, {"residual_ratio_vs_baseline"});
499 const double fail_pct = r.sample_count > 0 ? 100.0 * r.failure_rate : 0.0;
500
501 std::cout << kColorName << std::left << std::setw(static_cast<int>(name_width)) << r.name << kColorReset
502 << std::left;
503 for (size_t i = 0; i < max_args; ++i) {
504 std::cout << std::setw(10) << format_value(arg_at(i));
505 }
506 std::cout << std::setw(12) << format_value(std::isfinite(avg_log10_cond) ? avg_log10_cond : r.target_log10_cond)
507 << std::setw(10) << r.sample_count
508 << std::setw(10) << format_value(fail_pct);
509 if (cols.show_r) std::cout << std::setw(28) << format_value(mean_r);
510 if (cols.show_o) std::cout << std::setw(24) << format_value(mean_o);
511 if (cols.show_relerr) std::cout << std::setw(16) << format_value(mean_relerr);
512 if (cols.show_iterations_done) std::cout << std::setw(14) << format_value(mean_iterations_done);
513 if (cols.show_total_time_sec) std::cout << std::setw(14) << format_value(mean_total_time_sec);
514 if (cols.show_final_best_max) std::cout << std::setw(14) << format_value(mean_final_best_max);
515 if (cols.show_converged_fraction) std::cout << std::setw(14) << format_value(mean_converged_fraction);
516 if (cols.show_fill_ratio) std::cout << std::setw(14) << format_value(mean_fill_ratio);
517 if (cols.show_iter_ratio) std::cout << std::setw(14) << format_value(mean_iter_ratio);
518 if (cols.show_time_ratio) std::cout << std::setw(14) << format_value(mean_time_ratio);
519 if (cols.show_residual_ratio) std::cout << std::setw(14) << format_value(mean_residual_ratio);
520 std::cout << '\n';
521}
522
523inline void write_samples_csv(const std::string& path,
524 const std::vector<Result>& results) {
525 std::ofstream f(path);
526 if (!f.is_open()) {
527 std::cerr << "Failed to open CSV file: " << path << '\n';
528 return;
529 }
530
531 size_t max_args = 0;
532 std::vector<std::string> metric_names;
533 std::unordered_map<std::string, bool> metric_seen;
534 std::vector<std::string> tag_names;
535 std::unordered_map<std::string, bool> tag_seen;
536
537 for (const auto& r : results) {
538 max_args = std::max(max_args, r.args.size());
539 for (const auto& row : r.sample_rows) {
540 for (const auto& kv : row.metrics) {
541 if (!metric_seen[kv.first]) {
542 metric_names.push_back(kv.first);
543 metric_seen[kv.first] = true;
544 }
545 }
546 for (const auto& kv : row.tags) {
547 if (!tag_seen[kv.first]) {
548 tag_names.push_back(kv.first);
549 tag_seen[kv.first] = true;
550 }
551 }
552 }
553 }
554
555 f << "name";
556 for (size_t i = 0; i < max_args; ++i) f << ",arg" << i;
557 f << ",target_log10_cond,sample,ok,info_code,failure_reason";
558 for (const auto& t : tag_names) f << ",tag_" << t;
559 for (const auto& m : metric_names) f << ',' << m;
560 f << '\n';
561
562 for (const auto& r : results) {
563 for (const auto& row : r.sample_rows) {
564 f << '"' << r.name << '"';
565 for (size_t i = 0; i < max_args; ++i) {
566 if (i < r.args.size()) f << ',' << r.args[i];
567 else f << ',';
568 }
569 f << ',' << r.target_log10_cond
570 << ',' << row.sample_index
571 << ',' << (row.ok ? 1 : 0)
572 << ',' << row.info_code
573 << ',' << '"' << row.failure_reason << '"';
574
575 for (const auto& t : tag_names) {
576 auto it = row.tags.find(t);
577 if (it != row.tags.end()) f << ',' << '"' << it->second << '"';
578 else f << ',';
579 }
580
581 for (const auto& m : metric_names) {
582 auto it = row.metrics.find(m);
583 if (it != row.metrics.end()) f << ',' << it->second;
584 else f << ',';
585 }
586 f << '\n';
587 }
588 }
589}
590
591inline int RunRegisteredBenchmarks(const Config& cfg,
592 const std::string& csv_path,
593 const std::string& benchmark_filter,
594 const std::vector<std::string>& backends,
595 const std::vector<std::string>& types,
596 const std::vector<std::vector<double>>& args_override) {
597 std::vector<Result> results;
598
599 std::vector<double> cond_targets = cfg.log10_cond_values;
600 if (cond_targets.empty()) {
601 cond_targets.push_back(std::numeric_limits<double>::quiet_NaN());
602 }
603
604 const bool keep_samples = !csv_path.empty();
605 size_t max_args = 0;
606
607 for (auto& b : registry()) {
608 if (!match_filter(b, benchmark_filter, backends, types)) continue;
609
610 const auto* active_args = &b.args_list;
611 if (!args_override.empty()) active_args = &args_override;
612
613 const bool has_args = !active_args->empty();
614 const std::vector<std::vector<double>> empty_case{{}};
615 const auto& to_run = has_args ? *active_args : empty_case;
616
617 for (const auto& args : to_run) {
618 for (double log10_cond : cond_targets) {
619 Result r = run_benchmark(b, args, cfg, log10_cond, keep_samples);
620 max_args = std::max(max_args, r.args.size());
621 results.push_back(std::move(r));
622 }
623 }
624 }
625
626 const SummaryMetricColumns cols = detect_summary_columns(results);
627 size_t name_width = 50;
628 for (const auto& r : results) {
629 name_width = std::max(name_width, r.name.size() + 2);
630 }
631
632 print_summary_header(name_width, max_args, cols);
633 for (const auto& r : results) {
634 print_summary_row(r, name_width, max_args, cols);
635 }
636
637 if (!csv_path.empty()) {
638 write_samples_csv(csv_path, results);
639 }
640
641 return 0;
642}
643
646 std::vector<std::vector<double>> args_list;
647 std::string csv_file;
648 std::string benchmark_filter;
649 std::vector<std::string> backends;
650 std::vector<std::string> types;
651
652 /// `--help` was seen: usage has been printed and there is nothing to run.
653 /// MiniAccMain() turns it into exit status 0.
654 // A flag, never std::exit: this installed header must not terminate its host process.
655 bool help_requested = false;
656};
657
658inline CliOptions ParseCommandLine(int argc, char** argv) {
659 CliOptions opt;
660 std::vector<std::vector<double>> arg_values;
661
662 for (int i = 1; i < argc; ++i) {
663 std::string s(argv[i]);
664 if (s.rfind("--samples=", 0) == 0) {
665 opt.cfg.samples = std::stoul(s.substr(10));
666 } else if (s.rfind("--seed=", 0) == 0) {
667 opt.cfg.seed = static_cast<unsigned int>(std::stoul(s.substr(7)));
668 } else if (s.rfind("--log10-cond=", 0) == 0) {
670 } else if (s.rfind("--csv=", 0) == 0) {
671 opt.csv_file = s.substr(6);
672 } else if (s.rfind("--benchmark_filter=", 0) == 0) {
673 opt.benchmark_filter = s.substr(19);
674 } else if (s.rfind("--backend=", 0) == 0) {
675 opt.backends = parse_list(s.substr(10));
676 } else if (s.rfind("--type=", 0) == 0) {
677 opt.types = parse_list(s.substr(7));
678 } else if (s == "--help" || s == "-h") {
679 std::cout << "Usage: miniacc [options] [ARGS...]\n";
680 std::cout << "Options:\n";
681 std::cout << " --samples=N number of samples available to each case (default 1024)\n";
682 std::cout << " --seed=N base RNG seed (default 1234)\n";
683 std::cout << " --log10-cond=SPEC scalar, v1,v2,... or start:end:num\n";
684 std::cout << " --csv=FILE emit per-sample rows to CSV (summary always to terminal)\n";
685 std::cout << " --benchmark_filter=GLOB benchmark-name filter with * and ? wildcards\n";
686 std::cout << " --backend=LIST comma separated backends to run\n";
687 std::cout << " --type=LIST comma separated floating point types\n";
688 std::cout << " ARGS can be scalars, comma lists or start:end:num ranges\n";
689 // Return, do not exit; MiniAccMain turns the flag into exit status 0.
690 opt.help_requested = true;
691 return opt;
692 } else {
693 arg_values.push_back(parse_range_or_list_double(s));
694 }
695 }
696
697 if (!arg_values.empty()) {
698 std::vector<std::vector<double>> combos{{}};
699 for (const auto& vals : arg_values) {
700 std::vector<std::vector<double>> next;
701 for (const auto& c : combos) {
702 for (double v : vals) {
703 auto tmp = c;
704 tmp.push_back(v);
705 next.push_back(std::move(tmp));
706 }
707 }
708 combos = std::move(next);
709 }
710 opt.args_list = std::move(combos);
711 }
712
713 return opt;
714}
715
716/// @brief Parses the command line and runs every registered case that matches; the body of MINI_ACC_MAIN().
717/// @return 0, including after `--help`
718/// @ingroup api_internal_helpers
719inline int MiniAccMain(int argc, char** argv) {
720 const CliOptions opts = ParseCommandLine(argc, argv);
721 // The one place that decides what --help means for the PROCESS.
722 if (opts.help_requested) return 0;
723 return RunRegisteredBenchmarks(opts.cfg,
724 opts.csv_file,
725 opts.benchmark_filter,
726 opts.backends,
727 opts.types,
728 opts.args_list);
729}
730
731#define MINI_ACC_MAIN() \
732 int main(int argc, char** argv) { \
733 return miniacc::MiniAccMain(argc, argv); \
734 }
735
736} // namespace miniacc
Per-case context handed to a benchmark: its arguments, the run settings, and the sample sink.
Definition miniacc.hh:145
double arg(size_t idx) const
Definition miniacc.hh:156
double target_log10_cond() const
Definition miniacc.hh:166
const std::vector< SampleRow > & rows() const
Definition miniacc.hh:191
size_t samples() const
Definition miniacc.hh:164
void SetTag(const std::string &key, const std::string &value)
Definition miniacc.hh:169
State(std::vector< double > args, Config cfg, double target_log10_cond, std::string benchmark_name)
Definition miniacc.hh:147
unsigned int seed() const
Definition miniacc.hh:165
void RecordSample(const std::unordered_map< std::string, double > &metrics, bool ok=true, const std::string &failure_reason="", int info_code=0, const std::unordered_map< std::string, std::string > &tags={})
Definition miniacc.hh:173
int arg_int(size_t idx) const
Definition miniacc.hh:160
const std::string & benchmark_name() const
Definition miniacc.hh:167
int MiniAccMain(int argc, char **argv)
Parses the command line and runs every registered case that matches; the body of MINI_ACC_MAIN().
Definition miniacc.hh:719
double metric_mean_or_nan(const Result &r, const std::initializer_list< const char * > &keys)
Definition miniacc.hh:404
bool has_any_metric(const Result &r, const std::initializer_list< const char * > &keys)
Definition miniacc.hh:429
constexpr const char * kColorName
Definition miniacc.hh:42
MetricSummary summarize_metric(const std::vector< SampleRow > &rows, const std::string &metric_name)
Definition miniacc.hh:255
void write_samples_csv(const std::string &path, const std::vector< Result > &results)
Definition miniacc.hh:523
SummaryMetricColumns detect_summary_columns(const std::vector< Result > &results)
Definition miniacc.hh:434
std::function< void(State &)> BenchFunc
Definition miniacc.hh:202
std::vector< double > dedup_nearly_equal(const std::vector< double > &input, double eps=1e-12)
Definition miniacc.hh:126
Result run_benchmark(const Benchmark &b, const std::vector< double > &args, const Config &cfg, double target_log10_cond, bool keep_samples)
Definition miniacc.hh:288
constexpr const char * kColorReset
Definition miniacc.hh:40
CliOptions ParseCommandLine(int argc, char **argv)
Definition miniacc.hh:658
std::vector< std::string > parse_list(const std::string &str)
Definition miniacc.hh:116
constexpr const char * kColorHeader
Definition miniacc.hh:41
void print_summary_header(size_t name_width, size_t max_args, const SummaryMetricColumns &cols)
Definition miniacc.hh:452
void print_summary_row(const Result &r, size_t name_width, size_t max_args, const SummaryMetricColumns &cols)
Definition miniacc.hh:479
Benchmark * RegisterBenchmark(const std::string &name, BenchFunc func)
Definition miniacc.hh:250
std::string format_value(double v)
Definition miniacc.hh:397
bool match_filter(const Benchmark &b, const std::string &benchmark_filter, const std::vector< std::string > &backends, const std::vector< std::string > &types)
Definition miniacc.hh:330
std::vector< double > parse_range_or_list_double(const std::string &str)
Definition miniacc.hh:81
int RunRegisteredBenchmarks(const Config &cfg, const std::string &csv_path, const std::string &benchmark_filter, const std::vector< std::string > &backends, const std::vector< std::string > &types, const std::vector< std::vector< double > > &args_override)
Definition miniacc.hh:591
std::vector< Benchmark > & registry()
Definition miniacc.hh:245
A registered case: name, function, and its argument lists (Args, ArgRange, ArgsProduct).
Definition miniacc.hh:206
Benchmark * ArgRange(double start, double end, size_t num=5)
Definition miniacc.hh:218
std::string name
Definition miniacc.hh:207
std::vector< std::vector< double > > args_list
Definition miniacc.hh:209
Benchmark(const std::string &n, BenchFunc f)
Definition miniacc.hh:211
Benchmark * ArgsProduct(const std::vector< std::vector< double > > &ranges)
Definition miniacc.hh:227
Benchmark * Args(const std::vector< double > &a)
Definition miniacc.hh:213
std::vector< std::string > backends
Definition miniacc.hh:649
std::vector< std::string > types
Definition miniacc.hh:650
std::string csv_file
Definition miniacc.hh:647
bool help_requested
--help was seen: usage has been printed and there is nothing to run.
Definition miniacc.hh:655
std::vector< std::vector< double > > args_list
Definition miniacc.hh:646
std::string benchmark_filter
Definition miniacc.hh:648
Run-wide settings parsed from the command line.
Definition miniacc.hh:46
unsigned int seed
Definition miniacc.hh:48
std::vector< double > log10_cond_values
Definition miniacc.hh:49
size_t samples
Definition miniacc.hh:47
double target_log10_cond
Definition miniacc.hh:73
size_t sample_count
Definition miniacc.hh:74
std::unordered_map< std::string, MetricSummary > summary
Definition miniacc.hh:77
std::string name
Definition miniacc.hh:71
std::vector< SampleRow > sample_rows
Definition miniacc.hh:78
std::vector< double > args
Definition miniacc.hh:72
size_t failure_count
Definition miniacc.hh:75
double failure_rate
Definition miniacc.hh:76
size_t sample_index
Definition miniacc.hh:53
std::unordered_map< std::string, double > metrics
Definition miniacc.hh:57
std::string failure_reason
Definition miniacc.hh:56
std::unordered_map< std::string, std::string > tags
Definition miniacc.hh:58