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
28 static int64_t g_bytes_processed;
29 static int64_t g_benchmark_total_time_ns;
30 static int64_t g_benchmark_start_time_ns;
31
32 typedef std::map<std::string, ::testing::Benchmark*> BenchmarkMap;
33 typedef BenchmarkMap::iterator BenchmarkMapIt;
34 static BenchmarkMap g_benchmarks;
35 static int g_name_column_width = 20;
36
Round(int n)37 static 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
NanoTime()51 static 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
58 namespace testing {
59
Arg(int arg)60 Benchmark* Benchmark::Arg(int arg) {
61 args_.push_back(arg);
62 return this;
63 }
64
Name()65 const char* Benchmark::Name() {
66 return name_;
67 }
68
ShouldRun(int argc,char * argv[])69 bool 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
Register(const char * name,void (* fn)(int),void (* fn_range)(int,int))90 void 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
Run()103 void 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
RunRepeatedlyWithArg(int iterations,int arg)117 void 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
RunWithArg(int arg)131 void 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
SetBenchmarkBytesProcessed(int64_t x)175 void SetBenchmarkBytesProcessed(int64_t x) {
176 g_bytes_processed = x;
177 }
178
StopBenchmarkTiming()179 void 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
StartBenchmarkTiming()186 void StartBenchmarkTiming() {
187 if (g_benchmark_start_time_ns == 0) {
188 g_benchmark_start_time_ns = NanoTime();
189 }
190 }
191
main(int argc,char * argv[])192 int 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 int name_width = static_cast<int>(strlen(it->second->Name()));
200 g_name_column_width = std::max(g_name_column_width, name_width);
201 }
202
203 bool need_header = true;
204 for (BenchmarkMapIt it = g_benchmarks.begin(); it != g_benchmarks.end(); ++it) {
205 ::testing::Benchmark* b = it->second;
206 if (b->ShouldRun(argc, argv)) {
207 if (need_header) {
208 printf("%-*s %10s %10s\n", g_name_column_width, "", "iterations", "ns/op");
209 fflush(stdout);
210 need_header = false;
211 }
212 b->Run();
213 }
214 }
215
216 if (need_header) {
217 fprintf(stderr, "No matching benchmarks!\n");
218 fprintf(stderr, "Available benchmarks:\n");
219 for (BenchmarkMapIt it = g_benchmarks.begin(); it != g_benchmarks.end(); ++it) {
220 fprintf(stderr, " %s\n", it->second->Name());
221 }
222 exit(EXIT_FAILURE);
223 }
224
225 return 0;
226 }
227