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