1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#include "benchmark.h"
17#include <regex.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <string>
22#include <inttypes.h>
23#include <time.h>
24#include <map>
25
26static int64_t g_flops_processed;
27static int64_t g_benchmark_total_time_ns;
28static int64_t g_benchmark_start_time_ns;
29typedef std::map<std::string, ::testing::Benchmark*> BenchmarkMap;
30typedef BenchmarkMap::iterator BenchmarkMapIt;
31
32BenchmarkMap& gBenchmarks() {
33  static BenchmarkMap g_benchmarks;
34  return g_benchmarks;
35}
36
37static int g_name_column_width = 20;
38
39static int Round(int n) {
40  int base = 1;
41  while (base*10 < n) {
42    base *= 10;
43  }
44  if (n < 2*base) {
45    return 2*base;
46  }
47  if (n < 5*base) {
48    return 5*base;
49  }
50  return 10*base;
51}
52
53#ifdef __APPLE__
54  #include <mach/mach_time.h>
55  static mach_timebase_info_data_t g_time_info;
56  static void __attribute__((constructor)) init_info() {
57    mach_timebase_info(&g_time_info);
58  }
59#endif
60
61static int64_t NanoTime() {
62#if defined(__APPLE__)
63  uint64_t t = mach_absolute_time();
64  return t * g_time_info.numer / g_time_info.denom;
65#else
66  struct timespec t;
67  t.tv_sec = t.tv_nsec = 0;
68  clock_gettime(CLOCK_MONOTONIC, &t);
69  return static_cast<int64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
70#endif
71}
72
73namespace testing {
74Benchmark* Benchmark::Arg(int arg) {
75  args_.push_back(arg);
76  return this;
77}
78
79Benchmark* Benchmark::Range(int lo, int hi) {
80  const int kRangeMultiplier = 8;
81  if (hi < lo) {
82    int temp = hi;
83    hi = lo;
84    lo = temp;
85  }
86  while (lo < hi) {
87    args_.push_back(lo);
88    lo *= kRangeMultiplier;
89  }
90  // We always run the hi number.
91  args_.push_back(hi);
92  return this;
93}
94
95const char* Benchmark::Name() {
96  return name_;
97}
98bool Benchmark::ShouldRun(int argc, char* argv[]) {
99  if (argc == 1) {
100    return true;  // With no arguments, we run all benchmarks.
101  }
102  // Otherwise, we interpret each argument as a regular expression and
103  // see if any of our benchmarks match.
104  for (int i = 1; i < argc; i++) {
105    regex_t re;
106    if (regcomp(&re, argv[i], 0) != 0) {
107      fprintf(stderr, "couldn't compile \"%s\" as a regular expression!\n", argv[i]);
108      exit(EXIT_FAILURE);
109    }
110    int match = regexec(&re, name_, 0, NULL, 0);
111    regfree(&re);
112    if (match != REG_NOMATCH) {
113      return true;
114    }
115  }
116  return false;
117}
118void Benchmark::Register(const char* name, void (*fn)(int), void (*fn_range)(int, int)) {
119  name_ = name;
120  fn_ = fn;
121  fn_range_ = fn_range;
122  if (fn_ == NULL && fn_range_ == NULL) {
123    fprintf(stderr, "%s: missing function\n", name_);
124    exit(EXIT_FAILURE);
125  }
126  gBenchmarks().insert(std::make_pair(name, this));
127}
128void Benchmark::Run() {
129  if (fn_ != NULL) {
130    RunWithArg(0);
131  } else {
132    if (args_.empty()) {
133      fprintf(stderr, "%s: no args!\n", name_);
134      exit(EXIT_FAILURE);
135    }
136    for (size_t i = 0; i < args_.size(); ++i) {
137      RunWithArg(args_[i]);
138    }
139  }
140}
141void Benchmark::RunRepeatedlyWithArg(int iterations, int arg) {
142  g_flops_processed = 0;
143  g_benchmark_total_time_ns = 0;
144  g_benchmark_start_time_ns = NanoTime();
145  if (fn_ != NULL) {
146    fn_(iterations);
147  } else {
148    fn_range_(iterations, arg);
149  }
150  if (g_benchmark_start_time_ns != 0) {
151    g_benchmark_total_time_ns += NanoTime() - g_benchmark_start_time_ns;
152  }
153}
154void Benchmark::RunWithArg(int arg) {
155  // run once in case it's expensive
156  int iterations = 1;
157  RunRepeatedlyWithArg(iterations, arg);
158  while (g_benchmark_total_time_ns < 1e9 && iterations < 1e9) {
159    int last = iterations;
160    if (g_benchmark_total_time_ns/iterations == 0) {
161      iterations = 1e9;
162    } else {
163      iterations = 1e9 / (g_benchmark_total_time_ns/iterations);
164    }
165    iterations = std::max(last + 1, std::min(iterations + iterations/2, 100*last));
166    iterations = Round(iterations);
167    RunRepeatedlyWithArg(iterations, arg);
168  }
169  char throughput[100];
170  throughput[0] = '\0';
171  if (g_benchmark_total_time_ns > 0 && g_flops_processed > 0) {
172    double mflops_processed = static_cast<double>(g_flops_processed)/1e6;
173    double seconds = static_cast<double>(g_benchmark_total_time_ns)/1e9;
174    snprintf(throughput, sizeof(throughput), " %8.2f MFlops/s", mflops_processed/seconds);
175  }
176  char full_name[100];
177  if (fn_range_ != NULL) {
178    if (arg >= (1<<20)) {
179      snprintf(full_name, sizeof(full_name), "%s/%dM", name_, arg/(1<<20));
180    } else if (arg >= (1<<10)) {
181      snprintf(full_name, sizeof(full_name), "%s/%dK", name_, arg/(1<<10));
182    } else {
183      snprintf(full_name, sizeof(full_name), "%s/%d", name_, arg);
184    }
185  } else {
186    snprintf(full_name, sizeof(full_name), "%s", name_);
187  }
188  printf("%-*s %10d %10" PRId64 "%s\n", g_name_column_width, full_name,
189         iterations, g_benchmark_total_time_ns/iterations, throughput);
190  fflush(stdout);
191}
192}  // namespace testing
193void SetBenchmarkFlopsProcessed(int64_t x) {
194  g_flops_processed = x;
195}
196void StopBenchmarkTiming() {
197  if (g_benchmark_start_time_ns != 0) {
198    g_benchmark_total_time_ns += NanoTime() - g_benchmark_start_time_ns;
199  }
200  g_benchmark_start_time_ns = 0;
201}
202void StartBenchmarkTiming() {
203  if (g_benchmark_start_time_ns == 0) {
204    g_benchmark_start_time_ns = NanoTime();
205  }
206}
207int main(int argc, char* argv[]) {
208  if (gBenchmarks().empty()) {
209    fprintf(stderr, "No benchmarks registered!\n");
210    exit(EXIT_FAILURE);
211  }
212  for (BenchmarkMapIt it = gBenchmarks().begin(); it != gBenchmarks().end(); ++it) {
213    int name_width = static_cast<int>(strlen(it->second->Name()));
214    g_name_column_width = std::max(g_name_column_width, name_width);
215  }
216  bool need_header = true;
217  for (BenchmarkMapIt it = gBenchmarks().begin(); it != gBenchmarks().end(); ++it) {
218    ::testing::Benchmark* b = it->second;
219    if (b->ShouldRun(argc, argv)) {
220      if (need_header) {
221        printf("%-*s %10s %10s\n", g_name_column_width, "", "iterations", "ns/op");
222        fflush(stdout);
223        need_header = false;
224      }
225      b->Run();
226    }
227  }
228  if (need_header) {
229    fprintf(stderr, "No matching benchmarks!\n");
230    fprintf(stderr, "Available benchmarks:\n");
231    for (BenchmarkMapIt it = gBenchmarks().begin(); it != gBenchmarks().end(); ++it) {
232      fprintf(stderr, "  %s\n", it->second->Name());
233    }
234    exit(EXIT_FAILURE);
235  }
236  return 0;
237}
238