gperftools/benchmark/run_benchmark.cc

229 lines
6.9 KiB
C++

// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "run_benchmark.h"
#include "trivialre.h"
#include <functional>
#include <sstream>
#include <string>
#include <string_view>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
static constexpr double kTrialNSec = 0.3e9;
static double benchmark_duration_nsec = 3e9;
static int benchmark_repetitions = 3;
static std::function<bool(std::string_view bench)> benchmark_filter;
bool benchmark_list_only;
static
std::function<bool(std::string_view bench)> parse_filter_or_die(std::string_view filter) {
struct Policy {
bool failed{};
std::string_view msg{};
std::string_view at{};
void NoteError(std::string_view msg, std::string_view at) {
failed = true;
this->msg = msg;
this->at = at;
}
void StartedParsing(std::string_view str) {}
};
trivialre::re_compiler::C<trivialre::matchers::MatcherBuilder, Policy> compiler({});
trivialre::Matcher matcher = compiler.CompileOrDie(filter);
if (compiler.failed) {
fprintf(stderr, "failed to parse benchmark filter: '%.*s'.\nParse error: %.*s at %.*s\n",
(int)filter.size(), filter.data(),
(int)compiler.msg.size(), compiler.msg.data(),
(int)compiler.at.size(), compiler.at.data());
exit(1);
}
return [matcher] (std::string_view candidate) {
return trivialre::MatchSubstring(matcher, candidate);
};
}
void init_benchmark(int *argc, char ***argv) {
int n = *argc;
char **args = *argv;
for (int i = 1; i < n; i++) {
std::string_view a{args[i]};
if (a.substr(0, 2) != "--") {
fprintf(stderr, "benchmark only understands --long-form=<value> flags. got: %.*s\n",
(int)a.size(), a.data());
exit(1);
}
a.remove_prefix(2);
std::string_view rest;
auto has_prefix = [&] (std::string_view s) {
bool rv = (a.substr(0, s.size()) == s);
if (rv) {
rest = a.substr(s.size());
}
return rv;
};
if (a == "help") {
printf("%s --help\n"
" --benchmark_filter=<regex>\n"
" --benchmark_list\n"
" --benchmark_min_time=<seconds>\n"
" --benchmark_repetitions=<count>\n"
"\n", (*argv)[0]);
benchmark_list_only = true;
} else if (has_prefix("benchmark_min_time=")) {
char *end = nullptr;
double value = strtod(rest.data(), &end);
if (!end || *end) {
fprintf(stderr, "failed to parse benchmark_min_time argument: %s\n", args[i]);
exit(1);
}
benchmark_duration_nsec = value * 1e9;
// printf("benchmark_duration_nsec = %f\n", benchmark_duration_nsec);
} else if (has_prefix("benchmark_repetitions=")) {
char *end = nullptr;
long value = strtol(rest.data(), &end, 0);
if (!end || *end || value < 1 || static_cast<long>(static_cast<int>(value)) != value) {
fprintf(stderr, "failed to parse benchmark_repetitions argument: %s\n", args[i]);
exit(1);
}
benchmark_repetitions = value;
} else if (has_prefix("benchmark_filter=")) {
benchmark_filter = parse_filter_or_die(rest);
} else if (a == "benchmark_list") {
benchmark_list_only = true;
} else {
fprintf(stderr, "unknown flag: %.*s\n", (int)a.size(), a.data());
exit(1);
}
}
}
struct internal_bench {
bench_body body;
uintptr_t param;
};
static void run_body(struct internal_bench *b, long iterations)
{
b->body(iterations, b->param);
}
static double measure_once(struct internal_bench *b, long iterations)
{
struct timeval tv_before, tv_after;
int rv;
double time;
rv = gettimeofday(&tv_before, nullptr);
if (rv) {
perror("gettimeofday");
abort();
}
run_body(b, iterations);
rv = gettimeofday(&tv_after, nullptr);
if (rv) {
perror("gettimeofday");
abort();
}
tv_after.tv_sec -= tv_before.tv_sec;
time = tv_after.tv_sec * 1E6 + tv_after.tv_usec;
time -= tv_before.tv_usec;
time *= 1000;
return time;
}
static double run_benchmark(struct internal_bench *b)
{
long iterations = 128;
double nsec;
while (1) {
nsec = measure_once(b, iterations);
if (nsec > kTrialNSec) {
break;
}
iterations = ((unsigned long)iterations) << 1;
if (iterations <= 0) { // overflow
abort();
}
}
while (nsec < benchmark_duration_nsec) {
double target_iterations = iterations * benchmark_duration_nsec * 1.1 / nsec;
if (target_iterations > (double)LONG_MAX) {
abort();
}
iterations = target_iterations;
nsec = measure_once(b, iterations);
}
return nsec / iterations;
}
void report_benchmark(const char *name, bench_body body, uintptr_t param)
{
std::ostringstream full_name_stream(name, std::ios_base::ate);
if (param) {
full_name_stream << "(" << param << ")";
}
std::string full_name = full_name_stream.str();
if (benchmark_list_only) {
printf("known benchmark: %s\n", full_name.c_str());
return;
}
if (benchmark_filter && !benchmark_filter(std::string_view{full_name})) {
return;
}
struct internal_bench b = {.body = body, .param = param};
for (int i = 0; i < benchmark_repetitions; i++) {
int slen = printf("Benchmark: %s", full_name.c_str());
fflush(stdout);
double nsec = run_benchmark(&b);
int padding_size;
padding_size = 60 - slen;
if (padding_size < 1) {
padding_size = 1;
}
printf("%*c%f nsec (rate: %f Mops/sec)\n", padding_size, ' ', nsec, 1e9/nsec/1e6);
fflush(stdout);
}
}