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