feafbb13f8175ad1e98b36961d4d22f3bdc27d65
[benchmarks.git] / src / main.cpp
1 #include "all_benchmarks.h"
2 #include "harness.h"
3 #include <charconv>
4 #include <cstdlib>
5 #include <functional>
6 #include <initializer_list>
7 #include <iostream>
8 #include <limits>
9 #include <map>
10 #include <optional>
11 #include <ostream>
12 #include <string_view>
13 #include <system_error>
14 #include <type_traits>
15 #include <unordered_set>
16 #include <vector>
17
18 using namespace std::literals;
19
20 enum class OptionValueKind
21 {
22 None,
23 Required,
24 };
25
26 class OptionsParser;
27
28 struct Option final
29 {
30 char short_name = '\0';
31 std::string_view long_name = "", description = "";
32 bool required = false;
33 bool all_other_args_not_required = false;
34 OptionValueKind value_kind = OptionValueKind::None;
35 std::function<void(OptionsParser &parser,
36 std::optional<std::string_view> value)>
37 parse_value;
38 bool has_short_name() const
39 {
40 return short_name != '\0';
41 }
42 bool has_long_name() const
43 {
44 return !long_name.empty();
45 }
46 friend std::ostream &operator<<(std::ostream &os, const Option &option)
47 {
48 if (option.has_long_name())
49 {
50 os << "--" << option.long_name;
51 }
52 else if (option.has_short_name())
53 {
54 os << "-" << option.short_name;
55 }
56 else
57 {
58 os << "--<unnamed>";
59 }
60 return os;
61 }
62 };
63
64 class Options final
65 {
66 friend class OptionsParser;
67
68 private:
69 std::vector<Option> options;
70 std::map<char, std::size_t> short_options;
71 std::map<std::string_view, std::size_t> long_options;
72
73 public:
74 Options(std::initializer_list<Option> options_)
75 : options(options_), short_options(), long_options()
76 {
77 for (std::size_t i = 0; i < options.size(); i++)
78 {
79 auto &option = options[i];
80 if (option.has_short_name())
81 short_options[option.short_name] = i;
82 if (option.has_long_name())
83 long_options[option.long_name] = i;
84 }
85 }
86
87 public:
88 std::vector<std::string_view> parse(const char *const *argv) const;
89 };
90
91 class OptionsParser final
92 {
93 private:
94 const Options &options;
95 const char *const *argv;
96 std::string_view argv0;
97 std::vector<bool> seen_options;
98 bool check_required = true;
99 std::optional<std::size_t> current_option_index;
100
101 private:
102 const Option &current_option()
103 {
104 static const Option default_option{};
105 if (current_option_index)
106 {
107 return options.options[*current_option_index];
108 }
109 else
110 {
111 return default_option;
112 }
113 }
114
115 public:
116 OptionsParser(const Options &options, const char *const *argv)
117 : options(options), argv(argv + 1),
118 argv0(argv[0] ? argv[0] : "<none>"),
119 seen_options(options.options.size(), false), current_option_index()
120 {
121 if (!argv[0])
122 {
123 static const char *null[] = {nullptr};
124 this->argv = null;
125 help_and_exit("missing program name");
126 }
127 }
128
129 private:
130 void parse_option(std::size_t option_index, std::string_view prefix,
131 std::string_view name,
132 std::optional<std::string_view> value)
133 {
134 auto &option = options.options[option_index];
135 switch (option.value_kind)
136 {
137 case OptionValueKind::None:
138 if (value)
139 {
140 help_and_exit("value not allowed for ", prefix, name);
141 }
142 break;
143 case OptionValueKind::Required:
144 if (!value)
145 {
146 if (*argv)
147 {
148 value = *argv++;
149 }
150 else
151 {
152 help_and_exit("missing value for ", prefix, name);
153 }
154 }
155 break;
156 }
157 seen_options[option_index] = true;
158 if (option.all_other_args_not_required)
159 check_required = false;
160 current_option_index = option_index;
161 option.parse_value(*this, value);
162 current_option_index = std::nullopt;
163 }
164
165 public:
166 std::vector<std::string_view> parse()
167 {
168 std::vector<std::string_view> retval;
169 constexpr auto npos = std::string_view::npos;
170 while (*argv)
171 {
172 std::string_view arg = *argv++;
173 static constexpr std::string_view long_prefix = "--"sv;
174 static constexpr std::string_view short_prefix = "-"sv;
175 if (arg.rfind(long_prefix, 0) == 0) // it starts with `--`
176 {
177 arg.remove_prefix(long_prefix.size());
178 auto eq = arg.find('=');
179 if (eq == 0)
180 {
181 eq = npos;
182 }
183 if (arg.empty()) // just `--`
184 {
185 while (*argv)
186 {
187 retval.push_back(*argv++);
188 }
189 break;
190 }
191 auto name = arg.substr(0, eq);
192 std::optional<std::string_view> value;
193 if (eq != npos)
194 {
195 value = arg.substr(eq + 1);
196 }
197 auto iter = options.long_options.find(name);
198 if (iter == options.long_options.end())
199 {
200 help_and_exit("unknown option: ", long_prefix, name);
201 }
202 auto option_index = iter->second;
203 parse_option(option_index, long_prefix, name, value);
204 continue;
205 }
206 else if (arg.rfind(short_prefix, 0) == 0) // it starts with `-`
207 {
208 arg.remove_prefix(short_prefix.size());
209 if (arg.empty()) // just `-`
210 {
211 retval.push_back(short_prefix);
212 continue;
213 }
214 while (!arg.empty())
215 {
216 auto name = arg.substr(0, 1);
217 arg.remove_prefix(1);
218 auto iter = options.short_options.find(name[0]);
219 if (iter == options.short_options.end())
220 {
221 help_and_exit("unknown option: ", short_prefix, name);
222 }
223 auto option_index = iter->second;
224 std::optional<std::string_view> value;
225 switch (options.options[option_index].value_kind)
226 {
227 case OptionValueKind::None:
228 break;
229 case OptionValueKind::Required:
230 auto eq = arg.rfind('=', 0);
231 if (eq != npos)
232 {
233 value = arg.substr(eq + 1);
234 }
235 else if (!arg.empty())
236 {
237 value = arg;
238 }
239 arg = "";
240 break;
241 }
242 parse_option(option_index, short_prefix, name, value);
243 }
244 continue;
245 }
246 else
247 {
248 retval.push_back(arg);
249 }
250 }
251 if (check_required)
252 {
253 for (std::size_t i = 0; i < options.options.size(); i++)
254 {
255 auto &option = options.options[i];
256 if (option.required && !seen_options[i])
257 {
258 help_and_exit("missing required option ", option);
259 }
260 }
261 }
262 return retval;
263 }
264 template <typename... Options>
265 [[noreturn]] void help_and_exit(const Options &...error_msg) const
266 {
267 auto &os = sizeof...(error_msg) == 0 ? std::cout : std::cerr;
268 if (sizeof...(error_msg) != 0)
269 {
270 ((os << "Error: ") << ... << error_msg) << std::endl;
271 }
272 os << "Usage: " << argv0;
273 for (auto &option : options.options)
274 {
275 os << " ";
276 if (!option.required)
277 {
278 os << "[";
279 }
280 auto sep = "";
281 if (option.has_short_name())
282 {
283 os << "-" << option.short_name;
284 sep = "|";
285 }
286 if (option.has_long_name())
287 {
288 os << sep << "--" << option.long_name;
289 }
290 switch (option.value_kind)
291 {
292 case OptionValueKind::None:
293 break;
294 // TODO: case OptionValueKind::Optional:
295 case OptionValueKind::Required:
296 os << " <value>";
297 break;
298 }
299 if (!option.required)
300 {
301 os << "]";
302 }
303 }
304 os << std::endl << "Options:" << std::endl;
305 for (auto &option : options.options)
306 {
307 auto sep = "";
308 if (option.has_short_name())
309 {
310 os << "-" << option.short_name;
311 sep = "|";
312 }
313 if (option.has_long_name())
314 {
315 os << sep << "--" << option.long_name;
316 }
317 os << " " << option.description << std::endl;
318 }
319 std::exit(sizeof...(error_msg) == 0 ? 0 : 1);
320 }
321 template <typename Int, typename = void> struct ParseIntLimits;
322 template <typename Int>
323 struct ParseIntLimits<Int, std::enable_if_t<std::is_integral_v<Int>>> final
324 {
325 Int min_value = std::numeric_limits<Int>::min();
326 Int max_value = std::numeric_limits<Int>::max();
327 };
328 template <typename Int>
329 std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
330 std::optional<std::string_view> value, Int &i_value,
331 ParseIntLimits<Int> limits = {})
332 {
333 i_value = Int();
334 if (!value)
335 {
336 help_and_exit("missing value for ", current_option());
337 }
338 auto str = *value;
339 int base = 10;
340 if (0 == str.rfind("0x", 0) || 0 == str.rfind("0X", 0))
341 {
342 base = 16;
343 str.remove_prefix(2);
344 }
345 else if (0 == str.rfind("0o", 0) || 0 == str.rfind("0O", 0))
346 {
347 base = 8;
348 str.remove_prefix(2);
349 }
350 else if (0 == str.rfind("0b", 0) || 0 == str.rfind("0B", 0))
351 {
352 base = 2;
353 str.remove_prefix(2);
354 }
355 std::from_chars_result result = std::from_chars(
356 str.data(), str.data() + str.size(), i_value, base);
357 if (result.ptr != str.data() + str.size())
358 {
359 result.ec = std::errc::invalid_argument;
360 }
361 if (result.ec == std::errc())
362 {
363 if (i_value < limits.min_value || i_value > limits.max_value)
364 {
365 result.ec = std::errc::result_out_of_range;
366 }
367 }
368 if (result.ec == std::errc::result_out_of_range)
369 {
370 help_and_exit("value out of range: ", current_option(), "=",
371 *value);
372 }
373 else if (result.ec != std::errc())
374 {
375 help_and_exit("invalid value for: ", current_option());
376 }
377 }
378 template <typename Int>
379 std::enable_if_t<std::is_integral_v<Int>, void> parse_int(
380 std::optional<std::string_view> value, std::optional<Int> &i_value,
381 bool required = true, ParseIntLimits<Int> limits = {})
382 {
383 if (!required && !value)
384 {
385 i_value = std::nullopt;
386 return;
387 }
388 i_value.emplace();
389 this->parse_int(value, i_value.value(), limits);
390 }
391 };
392
393 inline std::vector<std::string_view> Options::parse(
394 const char *const *argv) const
395 {
396 return OptionsParser(*this, argv).parse();
397 }
398
399 int main(int, char **argv)
400 {
401 Config config{};
402 std::optional<std::unordered_set<std::string>> enabled_benchmarks;
403 Options options{
404 Option{
405 .short_name = 'h',
406 .long_name = "help",
407 .description = "Display usage and exit.",
408 .all_other_args_not_required = true,
409 .parse_value = [](OptionsParser &parser,
410 auto) { parser.help_and_exit(); },
411 },
412 Option{.short_name = 'j',
413 .long_name = "thread-count",
414 .description = "Number of threads to run on",
415 .value_kind = OptionValueKind::Required,
416 .parse_value =
417 [&](OptionsParser &parser, auto value) {
418 parser.parse_int(value, config.thread_count);
419 }},
420 Option{.short_name = 'n',
421 .long_name = "iter-count",
422 .description = "Number of iterations to run per thread",
423 .value_kind = OptionValueKind::Required,
424 .parse_value =
425 [&](OptionsParser &parser, auto value) {
426 parser.parse_int(value, config.iteration_count);
427 }},
428 Option{.long_name = "log2-mem-loc-count",
429 .description =
430 "Log base 2 of the number of memory locations to access",
431 .value_kind = OptionValueKind::Required,
432 .parse_value =
433 [&](OptionsParser &parser, auto value) {
434 parser.parse_int(
435 value, config.log2_memory_location_count,
436 {.max_value =
437 Config::max_sum_log2_mem_loc_count_and_stride -
438 config.log2_stride});
439 }},
440 Option{
441 .long_name = "log2-stride",
442 .description =
443 "Log base 2 of the stride used for accessing memory locations",
444 .value_kind = OptionValueKind::Required,
445 .parse_value =
446 [&](OptionsParser &parser, auto value) {
447 parser.parse_int(
448 value, config.log2_stride,
449 {.max_value =
450 Config::max_sum_log2_mem_loc_count_and_stride -
451 config.log2_memory_location_count});
452 }},
453 Option{
454 .short_name = 'b',
455 .long_name = "bench",
456 .description = "List of benchmarks that should be run",
457 .value_kind = OptionValueKind::Required,
458 .parse_value =
459 [&](OptionsParser &, std::optional<std::string_view> value) {
460 if (!enabled_benchmarks)
461 {
462 enabled_benchmarks.emplace();
463 }
464 enabled_benchmarks->emplace(value.value_or(""));
465 }},
466 };
467 OptionsParser parser(options, argv);
468 auto args = parser.parse();
469 if (!args.empty())
470 {
471 parser.help_and_exit("unexpected argument");
472 }
473 auto benchmarks = all_benchmarks(config);
474 if (enabled_benchmarks)
475 {
476 enabled_benchmarks->erase("");
477 enabled_benchmarks->erase("help");
478 enabled_benchmarks->erase("list");
479 if (enabled_benchmarks->empty())
480 {
481 std::cout << "Available Benchmarks:\n";
482 for (auto &benchmark : benchmarks)
483 {
484 std::cout << benchmark.name() << "\n";
485 }
486 std::cout << std::endl;
487 return 0;
488 }
489 std::unordered_set<std::string> unknown_benchmarks =
490 *enabled_benchmarks;
491 for (auto &benchmark : benchmarks)
492 {
493 unknown_benchmarks.erase(benchmark.name());
494 }
495 if (!unknown_benchmarks.empty())
496 {
497 parser.help_and_exit(
498 "unknown benchmark: ", *unknown_benchmarks.begin(),
499 "\nrun with `--bench=list` to see all supported benchmarks.");
500 }
501 }
502 auto thread_cache = BenchHarnessBase::get_thread_cache();
503 for (auto &benchmark : benchmarks)
504 {
505 if (enabled_benchmarks && !enabled_benchmarks->count(benchmark.name()))
506 {
507 continue;
508 }
509 std::cout << "Running: " << benchmark.name() << std::endl;
510 benchmark.run(config);
511 }
512 std::cout << std::endl;
513 return 0;
514 }