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 28static int64_t g_bytes_processed; 29static int64_t g_benchmark_total_time_ns; 30static int64_t g_benchmark_start_time_ns; 31 32typedef std::map<std::string, ::testing::Benchmark*> BenchmarkMap; 33typedef BenchmarkMap::iterator BenchmarkMapIt; 34static BenchmarkMap g_benchmarks; 35static int g_name_column_width = 20; 36 37static 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 51static 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 58namespace testing { 59 60Benchmark* Benchmark::Arg(int arg) { 61 args_.push_back(arg); 62 return this; 63} 64 65const char* Benchmark::Name() { 66 return name_; 67} 68 69bool 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 90void 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 103void 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 117void 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 131void 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 175void SetBenchmarkBytesProcessed(int64_t x) { 176 g_bytes_processed = x; 177} 178 179void 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 186void StartBenchmarkTiming() { 187 if (g_benchmark_start_time_ns == 0) { 188 g_benchmark_start_time_ns = NanoTime(); 189 } 190} 191 192int 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