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