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