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
17#include "benchmark.h"
18
19#include <regex.h>
20#include <stdio.h>
21#include <stdlib.h>
22
23#include <string>
24#include <map>
25
26static int64_t gBytesProcessed;
27static int64_t gBenchmarkTotalTimeNs;
28static int64_t gBenchmarkStartTimeNs;
29
30typedef std::map<std::string, ::testing::Benchmark*> BenchmarkMap;
31typedef BenchmarkMap::iterator BenchmarkMapIt;
32static BenchmarkMap gBenchmarks;
33
34static int Round(int n) {
35  int base = 1;
36  while (base*10 < n) {
37    base *= 10;
38  }
39  if (n < 2*base) {
40    return 2*base;
41  }
42  if (n < 5*base) {
43    return 5*base;
44  }
45  return 10*base;
46}
47
48static int64_t NanoTime() {
49  struct timespec t;
50  t.tv_sec = t.tv_nsec = 0;
51  clock_gettime(CLOCK_MONOTONIC, &t);
52  return static_cast<int64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec;
53}
54
55namespace testing {
56
57Benchmark* Benchmark::Arg(int arg) {
58  args_.push_back(arg);
59  return this;
60}
61
62const char* Benchmark::Name() {
63  return name_;
64}
65
66bool Benchmark::ShouldRun(int argc, char* argv[]) {
67  if (argc == 1) {
68    return true;  // With no arguments, we run all benchmarks.
69  }
70  // Otherwise, we interpret each argument as a regular expression and
71  // see if any of our benchmarks match.
72  for (int i = 1; i < argc; i++) {
73    regex_t re;
74    if (regcomp(&re, argv[i], 0) != 0) {
75      fprintf(stderr, "couldn't compile \"%s\" as a regular expression!\n", argv[i]);
76      exit(EXIT_FAILURE);
77    }
78    int match = regexec(&re, name_, 0, NULL, 0);
79    regfree(&re);
80    if (match != REG_NOMATCH) {
81      return true;
82    }
83  }
84  return false;
85}
86
87void Benchmark::Register(const char* name, void (*fn)(int), void (*fn_range)(int, int)) {
88  name_ = name;
89  fn_ = fn;
90  fn_range_ = fn_range;
91
92  if (fn_ == NULL && fn_range_ == NULL) {
93    fprintf(stderr, "%s: missing function\n", name_);
94    exit(EXIT_FAILURE);
95  }
96
97  gBenchmarks.insert(std::make_pair(name, this));
98}
99
100void Benchmark::Run() {
101  if (fn_ != NULL) {
102    RunWithArg(0);
103  } else {
104    if (args_.empty()) {
105      fprintf(stderr, "%s: no args!\n", name_);
106      exit(EXIT_FAILURE);
107    }
108    for (size_t i = 0; i < args_.size(); ++i) {
109      RunWithArg(args_[i]);
110    }
111  }
112}
113
114void Benchmark::RunRepeatedlyWithArg(int iterations, int arg) {
115  gBytesProcessed = 0;
116  gBenchmarkTotalTimeNs = 0;
117  gBenchmarkStartTimeNs = NanoTime();
118  if (fn_ != NULL) {
119    fn_(iterations);
120  } else {
121    fn_range_(iterations, arg);
122  }
123  if (gBenchmarkStartTimeNs != 0) {
124    gBenchmarkTotalTimeNs += NanoTime() - gBenchmarkStartTimeNs;
125  }
126}
127
128void Benchmark::RunWithArg(int arg) {
129  // run once in case it's expensive
130  int iterations = 1;
131  RunRepeatedlyWithArg(iterations, arg);
132  while (gBenchmarkTotalTimeNs < 1e9 && iterations < 1e9) {
133    int last = iterations;
134    if (gBenchmarkTotalTimeNs/iterations == 0) {
135      iterations = 1e9;
136    } else {
137      iterations = 1e9 / (gBenchmarkTotalTimeNs/iterations);
138    }
139    iterations = std::max(last + 1, std::min(iterations + iterations/2, 100*last));
140    iterations = Round(iterations);
141    RunRepeatedlyWithArg(iterations, arg);
142  }
143
144  char throughput[100];
145  throughput[0] = '\0';
146  if (gBenchmarkTotalTimeNs > 0 && gBytesProcessed > 0) {
147    double mib_processed = static_cast<double>(gBytesProcessed)/1e6;
148    double seconds = static_cast<double>(gBenchmarkTotalTimeNs)/1e9;
149    snprintf(throughput, sizeof(throughput), " %8.2f MiB/s", mib_processed/seconds);
150  }
151
152  char full_name[100];
153  if (fn_range_ != NULL) {
154    if (arg >= (1<<20)) {
155      snprintf(full_name, sizeof(full_name), "%s/%dM", name_, arg/(1<<20));
156    } else if (arg >= (1<<10)) {
157      snprintf(full_name, sizeof(full_name), "%s/%dK", name_, arg/(1<<10));
158    } else {
159      snprintf(full_name, sizeof(full_name), "%s/%d", name_, arg);
160    }
161  } else {
162    snprintf(full_name, sizeof(full_name), "%s", name_);
163  }
164
165  printf("%-20s %10lld %10lld%s\n", full_name,
166         static_cast<int64_t>(iterations), gBenchmarkTotalTimeNs/iterations, throughput);
167  fflush(stdout);
168}
169
170}  // namespace testing
171
172void SetBenchmarkBytesProcessed(int64_t x) {
173  gBytesProcessed = x;
174}
175
176void StopBenchmarkTiming() {
177  if (gBenchmarkStartTimeNs != 0) {
178    gBenchmarkTotalTimeNs += NanoTime() - gBenchmarkStartTimeNs;
179  }
180  gBenchmarkStartTimeNs = 0;
181}
182
183void StartBenchmarkTiming() {
184  if (gBenchmarkStartTimeNs == 0) {
185    gBenchmarkStartTimeNs = NanoTime();
186  }
187}
188
189int main(int argc, char* argv[]) {
190  if (gBenchmarks.empty()) {
191    fprintf(stderr, "No benchmarks registered!\n");
192    exit(EXIT_FAILURE);
193  }
194
195  bool need_header = true;
196  for (BenchmarkMapIt it = gBenchmarks.begin(); it != gBenchmarks.end(); ++it) {
197    ::testing::Benchmark* b = it->second;
198    if (b->ShouldRun(argc, argv)) {
199      if (need_header) {
200        printf("%-20s %10s %10s\n", "", "iterations", "ns/op");
201        fflush(stdout);
202        need_header = false;
203      }
204      b->Run();
205    }
206  }
207
208  if (need_header) {
209    fprintf(stderr, "No matching benchmarks!\n");
210    fprintf(stderr, "Available benchmarks:\n");
211    for (BenchmarkMapIt it = gBenchmarks.begin(); it != gBenchmarks.end(); ++it) {
212      fprintf(stderr, "  %s\n", it->second->Name());
213    }
214    exit(EXIT_FAILURE);
215  }
216
217  return 0;
218}
219