1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #pragma once
6 
7 #include <stdio.h>
8 #include <fbl/function.h>
9 
10 #include <zircon/syscalls.h>
11 #include <lib/zx/time.h>
12 
13 #include <utility>
14 
15 constexpr unsigned kWarmUpIterations = 10;
16 // N.B. This value can't be so large that the buffer fills in oneshot mode.
17 // The benchmark will assert-fail if the buffer fills: Otherwise the benchmark
18 // is invalid.
19 constexpr unsigned kDefaultRunIterations = 100000;
20 
21 // The number of test runs to do.
22 // We do this many runs and report min,max,average.
23 // We do this because there's some variability in the runs, and this helps
24 // identify when it's happening and cope.
25 constexpr unsigned kNumTestRuns = 10;
26 
27 // Measures how long it takes to run some number of iterations of a closure.
28 // Returns a value in microseconds.
29 template <typename T>
Measure(unsigned iterations,const T & closure)30 float Measure(unsigned iterations, const T& closure) {
31     zx_ticks_t start = zx_ticks_get();
32     for (unsigned i = 0; i < iterations; ++i) {
33         closure();
34     }
35     zx_ticks_t stop = zx_ticks_get();
36     return (static_cast<float>(stop - start) * 1000000.f /
37             static_cast<float>(zx_ticks_per_second()));
38 }
39 
40 using thunk = fbl::Function<void ()>;
41 
42 // Runs a closure repeatedly and prints its timing.
43 template <typename T>
RunAndMeasure(const char * test_name,const char * spec_name,unsigned iterations,const T & closure,thunk setup,thunk teardown)44 void RunAndMeasure(const char* test_name, const char* spec_name,
45                    unsigned iterations, const T& closure,
46                    thunk setup, thunk teardown) {
47     printf("\n* %s: %s ...\n", spec_name, test_name);
48 
49     setup();
50     float warm_up_time = Measure(kWarmUpIterations, closure);
51     teardown();
52     printf("  - warm-up: %u iterations in %.3f us, %.3f us per iteration\n",
53            kWarmUpIterations, warm_up_time, warm_up_time / kWarmUpIterations);
54 
55     float run_times[kNumTestRuns];
56     for (unsigned i = 0; i < kNumTestRuns; ++i) {
57         setup();
58         run_times[i] = Measure(iterations, closure);
59         teardown();
60         zx::nanosleep(zx::deadline_after(zx::msec(10)));
61     }
62 
63     float min = 0, max = 0;
64     float cumulative = 0;
65     for (const auto rt : run_times) {
66         if (min == 0 || min > rt)
67             min = rt;
68         if (max == 0 || max < rt)
69             max = rt;
70         cumulative += rt;
71     }
72     float average = cumulative / kNumTestRuns;
73 
74     printf("  - run: %u test runs, %u iterations per run\n",
75            kNumTestRuns, iterations);
76     printf("  - total (usec): min: %.3f, max: %.3f, ave: %.3f\n",
77            min, max, average);
78     printf("  - per-iteration (usec): min: %.3f\n",
79            // The static cast is to avoid a "may change value" warning.
80            min / static_cast<float>(iterations));
81 }
82 
83 template <typename T>
RunAndMeasure(const char * test_name,const char * spec_name,const T & closure,thunk setup,thunk teardown)84 void RunAndMeasure(const char* test_name, const char* spec_name,
85                    const T& closure, thunk setup, thunk teardown) {
86     RunAndMeasure(test_name, spec_name, kDefaultRunIterations, closure,
87                   std::move(setup), std::move(teardown));
88 }
89