• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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