/* Copyright 2022 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Benchmark utility functions. */ #ifndef __CROS_EC_BENCHMARK_H #define __CROS_EC_BENCHMARK_H #include #include #include #include #include extern "C" { #include "clock.h" #include "console.h" #include "timer.h" #include "util.h" #include "watchdog.h" } /* Benchmark execution options */ struct BenchmarkOptions { /* Number of test iterations */ int num_iterations = 10; /* Whether to reload the watchdog between executions of f() */ bool reload_watchdog = true; /* Whether to enable fast CPU clock during the test (when supported) */ bool use_fast_cpu = true; }; /* The result of a benchmark run with various timing metrics. * All time measurements are in micro seconds, except the average that * is captured in nanoseconds for increased resolution. */ struct BenchmarkResult { /* Name of the test, used when printing results */ std::string_view name; /* Total elapsed time (us) for all iterations */ uint32_t elapsed_time; /* Average elapsed time (us) for a single iteration */ uint32_t average_time; /* Minimum elapsed time (us) for a single iteration */ uint32_t min_time; /* Maximum elapsed time (us) for a single iteration */ uint32_t max_time; /* Compare two BenchmarkResult structs and print delta between baseline * and other. */ static void compare(const BenchmarkResult &baseline, const BenchmarkResult &other) { auto print_comparison = [](std::string_view title, uint32_t baseline, uint32_t other) { ccprintf(" %7s (us): %9u %9u %+9d (%+d%%)\n", title.data(), baseline, other, other - baseline, 100 * (static_cast(other) - static_cast(baseline)) / static_cast(baseline)); }; ccprintf("-----------------------------------------------\n"); ccprintf("Compare: %s vs %s\n", baseline.name.data(), other.name.data()); ccprintf("-----------------------------------------------\n"); print_comparison("Elapsed", baseline.elapsed_time, other.elapsed_time); print_comparison("Min", baseline.min_time, other.min_time); print_comparison("Max", baseline.max_time, other.max_time); print_comparison("Avg", baseline.average_time, other.average_time); cflush(); } }; /* Benchmark main class responsible for running the experiments and * collecting/printing the results. * Note that the implementation intentionally avoid dynamic memory allocations * and stores up to MAX_NUM_RESULTS results into a std::array. */ template class Benchmark { public: explicit Benchmark(const BenchmarkOptions &options = BenchmarkOptions()) : options_(options){}; /* Run benchmark of the function f(). * * TODO(b/253099367): replace std::optional with StatusOr */ std::optional run(const std::string_view benchmark_name, std::function f) { if (benchmark_name.empty()) { ccprintf("%s: benchmark_name cannot be empty\n", __func__); return {}; } if (num_results_ >= MAX_NUM_RESULTS) { ccprintf("%s: cannot store new BenchmarkResults\n", __func__); return {}; } BenchmarkResult &result = results_[num_results_++]; result.name = benchmark_name; result.elapsed_time = 0; if (options_.use_fast_cpu) clock_enable_module(MODULE_FAST_CPU, 1); bool valid_min_max = false; for (int i = 0; i < options_.num_iterations; ++i) { timestamp_t start_time = get_time(); f(); uint32_t iteration_time = time_since32(start_time); if (options_.reload_watchdog) watchdog_reload(); if (valid_min_max) { result.max_time = MAX(result.max_time, iteration_time); result.min_time = MIN(result.min_time, iteration_time); } else { result.max_time = iteration_time; result.min_time = iteration_time; valid_min_max = true; } result.elapsed_time += iteration_time; } if (options_.use_fast_cpu) clock_enable_module(MODULE_FAST_CPU, 0); result.average_time = (result.elapsed_time) / options_.num_iterations; return result; } void print_results() const { for (int i = 0; i < num_results_; ++i) { auto &result = results_[i]; ccprintf("------------------------------\n"); ccprintf("Benchmark: %s\n", result.name.data()); ccprintf("------------------------------\n"); ccprintf(" Iterations: %u\n", options_.num_iterations); ccprintf(" Elapsed (us): %u\n", result.elapsed_time); ccprintf(" Min (us): %u\n", result.min_time); ccprintf(" Max (us): %u\n", result.max_time); ccprintf(" Avg (us): %u\n", result.average_time); cflush(); } } private: const BenchmarkOptions options_; std::array results_; int num_results_ = 0; }; #endif /* __CROS_EC_BENCHMARK_H */