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
26 static int64_t g_flops_processed;
27 static int64_t g_benchmark_total_time_ns;
28 static int64_t g_benchmark_start_time_ns;
29 typedef std::map<std::string, ::testing::Benchmark*> BenchmarkMap;
30 typedef BenchmarkMap::iterator BenchmarkMapIt;
31
gBenchmarks()32 BenchmarkMap& gBenchmarks() {
33 static BenchmarkMap g_benchmarks;
34 return g_benchmarks;
35 }
36
37 static int g_name_column_width = 20;
38
Round(int n)39 static 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;
init_info()56 static void __attribute__((constructor)) init_info() {
57 mach_timebase_info(&g_time_info);
58 }
59 #endif
60
NanoTime()61 static 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
73 namespace testing {
Arg(int arg)74 Benchmark* Benchmark::Arg(int arg) {
75 args_.push_back(arg);
76 return this;
77 }
78
Range(int lo,int hi)79 Benchmark* 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
Name()95 const char* Benchmark::Name() {
96 return name_;
97 }
ShouldRun(int argc,char * argv[])98 bool 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 }
Register(const char * name,void (* fn)(int),void (* fn_range)(int,int))118 void 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 }
Run()128 void 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 }
RunRepeatedlyWithArg(int iterations,int arg)141 void 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 }
RunWithArg(int arg)154 void 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
SetBenchmarkFlopsProcessed(int64_t x)193 void SetBenchmarkFlopsProcessed(int64_t x) {
194 g_flops_processed = x;
195 }
StopBenchmarkTiming()196 void 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 }
StartBenchmarkTiming()202 void StartBenchmarkTiming() {
203 if (g_benchmark_start_time_ns == 0) {
204 g_benchmark_start_time_ns = NanoTime();
205 }
206 }
main(int argc,char * argv[])207 int 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