|  | /* | 
|  | * Copyright (C) 2012 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  | #include "benchmark.h" | 
|  | #include <regex.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <string> | 
|  | #include <inttypes.h> | 
|  | #include <time.h> | 
|  | #include <map> | 
|  |  | 
|  | static int64_t g_flops_processed; | 
|  | static int64_t g_benchmark_total_time_ns; | 
|  | static int64_t g_benchmark_start_time_ns; | 
|  | typedef std::map<std::string, ::testing::Benchmark*> BenchmarkMap; | 
|  | typedef BenchmarkMap::iterator BenchmarkMapIt; | 
|  |  | 
|  | BenchmarkMap& gBenchmarks() { | 
|  | static BenchmarkMap g_benchmarks; | 
|  | return g_benchmarks; | 
|  | } | 
|  |  | 
|  | static int g_name_column_width = 20; | 
|  |  | 
|  | static int Round(int n) { | 
|  | int base = 1; | 
|  | while (base * 10 < n) { | 
|  | base *= 10; | 
|  | } | 
|  | if (n < 2 * base) { | 
|  | return 2 * base; | 
|  | } | 
|  | if (n < 5 * base) { | 
|  | return 5 * base; | 
|  | } | 
|  | return 10 * base; | 
|  | } | 
|  |  | 
|  | #ifdef __APPLE__ | 
|  | #include <mach/mach_time.h> | 
|  | static mach_timebase_info_data_t g_time_info; | 
|  | static void __attribute__((constructor)) init_info() { mach_timebase_info(&g_time_info); } | 
|  | #endif | 
|  |  | 
|  | static int64_t NanoTime() { | 
|  | #if defined(__APPLE__) | 
|  | uint64_t t = mach_absolute_time(); | 
|  | return t * g_time_info.numer / g_time_info.denom; | 
|  | #else | 
|  | struct timespec t; | 
|  | t.tv_sec = t.tv_nsec = 0; | 
|  | clock_gettime(CLOCK_MONOTONIC, &t); | 
|  | return static_cast<int64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | namespace testing { | 
|  | Benchmark* Benchmark::Arg(int arg) { | 
|  | args_.push_back(arg); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | Benchmark* Benchmark::Range(int lo, int hi) { | 
|  | const int kRangeMultiplier = 8; | 
|  | if (hi < lo) { | 
|  | int temp = hi; | 
|  | hi = lo; | 
|  | lo = temp; | 
|  | } | 
|  | while (lo < hi) { | 
|  | args_.push_back(lo); | 
|  | lo *= kRangeMultiplier; | 
|  | } | 
|  | // We always run the hi number. | 
|  | args_.push_back(hi); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | const char* Benchmark::Name() { return name_; } | 
|  | bool Benchmark::ShouldRun(int argc, char* argv[]) { | 
|  | if (argc == 1) { | 
|  | return true;  // With no arguments, we run all benchmarks. | 
|  | } | 
|  | // Otherwise, we interpret each argument as a regular expression and | 
|  | // see if any of our benchmarks match. | 
|  | for (int i = 1; i < argc; i++) { | 
|  | regex_t re; | 
|  | if (regcomp(&re, argv[i], 0) != 0) { | 
|  | fprintf(stderr, "couldn't compile \"%s\" as a regular expression!\n", argv[i]); | 
|  | exit(EXIT_FAILURE); | 
|  | } | 
|  | int match = regexec(&re, name_, 0, NULL, 0); | 
|  | regfree(&re); | 
|  | if (match != REG_NOMATCH) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  | void Benchmark::Register(const char* name, void (*fn)(int), void (*fn_range)(int, int)) { | 
|  | name_ = name; | 
|  | fn_ = fn; | 
|  | fn_range_ = fn_range; | 
|  | if (fn_ == NULL && fn_range_ == NULL) { | 
|  | fprintf(stderr, "%s: missing function\n", name_); | 
|  | exit(EXIT_FAILURE); | 
|  | } | 
|  | gBenchmarks().insert(std::make_pair(name, this)); | 
|  | } | 
|  | void Benchmark::Run() { | 
|  | if (fn_ != NULL) { | 
|  | RunWithArg(0); | 
|  | } else { | 
|  | if (args_.empty()) { | 
|  | fprintf(stderr, "%s: no args!\n", name_); | 
|  | exit(EXIT_FAILURE); | 
|  | } | 
|  | for (size_t i = 0; i < args_.size(); ++i) { | 
|  | RunWithArg(args_[i]); | 
|  | } | 
|  | } | 
|  | } | 
|  | void Benchmark::RunRepeatedlyWithArg(int iterations, int arg) { | 
|  | g_flops_processed = 0; | 
|  | g_benchmark_total_time_ns = 0; | 
|  | g_benchmark_start_time_ns = NanoTime(); | 
|  | if (fn_ != NULL) { | 
|  | fn_(iterations); | 
|  | } else { | 
|  | fn_range_(iterations, arg); | 
|  | } | 
|  | if (g_benchmark_start_time_ns != 0) { | 
|  | g_benchmark_total_time_ns += NanoTime() - g_benchmark_start_time_ns; | 
|  | } | 
|  | } | 
|  | void Benchmark::RunWithArg(int arg) { | 
|  | // run once in case it's expensive | 
|  | int iterations = 1; | 
|  | RunRepeatedlyWithArg(iterations, arg); | 
|  | while (g_benchmark_total_time_ns < 1e9 && iterations < 1e9) { | 
|  | int last = iterations; | 
|  | if (g_benchmark_total_time_ns / iterations == 0) { | 
|  | iterations = 1e9; | 
|  | } else { | 
|  | iterations = 1e9 / (g_benchmark_total_time_ns / iterations); | 
|  | } | 
|  | iterations = std::max(last + 1, std::min(iterations + iterations / 2, 100 * last)); | 
|  | iterations = Round(iterations); | 
|  | RunRepeatedlyWithArg(iterations, arg); | 
|  | } | 
|  | char throughput[100]; | 
|  | throughput[0] = '\0'; | 
|  | if (g_benchmark_total_time_ns > 0 && g_flops_processed > 0) { | 
|  | double mflops_processed = static_cast<double>(g_flops_processed) / 1e6; | 
|  | double seconds = static_cast<double>(g_benchmark_total_time_ns) / 1e9; | 
|  | snprintf(throughput, sizeof(throughput), " %8.2f MFlops/s", mflops_processed / seconds); | 
|  | } | 
|  | char full_name[100]; | 
|  | if (fn_range_ != NULL) { | 
|  | if (arg >= (1 << 20)) { | 
|  | snprintf(full_name, sizeof(full_name), "%s/%dM", name_, arg / (1 << 20)); | 
|  | } else if (arg >= (1 << 10)) { | 
|  | snprintf(full_name, sizeof(full_name), "%s/%dK", name_, arg / (1 << 10)); | 
|  | } else { | 
|  | snprintf(full_name, sizeof(full_name), "%s/%d", name_, arg); | 
|  | } | 
|  | } else { | 
|  | snprintf(full_name, sizeof(full_name), "%s", name_); | 
|  | } | 
|  | printf("%-*s %10d %10" PRId64 "%s\n", g_name_column_width, full_name, iterations, | 
|  | g_benchmark_total_time_ns / iterations, throughput); | 
|  | fflush(stdout); | 
|  | } | 
|  | }  // namespace testing | 
|  | void SetBenchmarkFlopsProcessed(int64_t x) { g_flops_processed = x; } | 
|  | void StopBenchmarkTiming() { | 
|  | if (g_benchmark_start_time_ns != 0) { | 
|  | g_benchmark_total_time_ns += NanoTime() - g_benchmark_start_time_ns; | 
|  | } | 
|  | g_benchmark_start_time_ns = 0; | 
|  | } | 
|  | void StartBenchmarkTiming() { | 
|  | if (g_benchmark_start_time_ns == 0) { | 
|  | g_benchmark_start_time_ns = NanoTime(); | 
|  | } | 
|  | } | 
|  | int main(int argc, char* argv[]) { | 
|  | if (gBenchmarks().empty()) { | 
|  | fprintf(stderr, "No benchmarks registered!\n"); | 
|  | exit(EXIT_FAILURE); | 
|  | } | 
|  | for (BenchmarkMapIt it = gBenchmarks().begin(); it != gBenchmarks().end(); ++it) { | 
|  | int name_width = static_cast<int>(strlen(it->second->Name())); | 
|  | g_name_column_width = std::max(g_name_column_width, name_width); | 
|  | } | 
|  | bool need_header = true; | 
|  | for (BenchmarkMapIt it = gBenchmarks().begin(); it != gBenchmarks().end(); ++it) { | 
|  | ::testing::Benchmark* b = it->second; | 
|  | if (b->ShouldRun(argc, argv)) { | 
|  | if (need_header) { | 
|  | printf("%-*s %10s %10s\n", g_name_column_width, "", "iterations", "ns/op"); | 
|  | fflush(stdout); | 
|  | need_header = false; | 
|  | } | 
|  | b->Run(); | 
|  | } | 
|  | } | 
|  | if (need_header) { | 
|  | fprintf(stderr, "No matching benchmarks!\n"); | 
|  | fprintf(stderr, "Available benchmarks:\n"); | 
|  | for (BenchmarkMapIt it = gBenchmarks().begin(); it != gBenchmarks().end(); ++it) { | 
|  | fprintf(stderr, "  %s\n", it->second->Name()); | 
|  | } | 
|  | exit(EXIT_FAILURE); | 
|  | } | 
|  | return 0; | 
|  | } |