blob: 3d6cdabbd94c59f3d0c57b873c0d34d3ef50edd6 [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "bench/Benchmark.h"
#include "tools/flags/CommandLineFlags.h"
#include "tools/testrunners/benchmark/target/BenchmarkTarget.h"
#include "tools/testrunners/common/TestRunner.h"
static DEFINE_int(maxCalibrationAttempts,
3,
"Try up to this many times to guess loops for a benchmark, or skip the "
"benchmark.");
static DEFINE_double(overheadGoal,
0.0001,
"Loop until timer overhead is at most this fraction of our measurements.");
static DEFINE_int(overheadLoops, 100000, "Loops to estimate timer overhead.");
// Defined in BazelBenchmarkTestRunner.cpp.
SkString humanize(double ms);
void BenchmarkTarget::printGlobalStats() {}
class RasterBenchmarkTarget : public BenchmarkTarget {
public:
RasterBenchmarkTarget(std::unique_ptr<SurfaceManager> surfaceManager, Benchmark* benchmark)
: BenchmarkTarget(std::move(surfaceManager), benchmark) {}
Benchmark::Backend getBackend() const override { return Benchmark::Backend::kRaster; }
// Based on nanobench's setup_cpu_bench():
// https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#466.
std::tuple<int, bool> autoTuneLoops() const override {
// Estimate timer overhead. Based on:
// https://skia.googlesource.com/skia/+/a063eaeaf1e09e4d6f42e0f44a5723622a46d21c/bench/nanobench.cpp#402.
double overhead = 0;
for (int i = 0; i < FLAGS_overheadLoops; i++) {
double start = nowMs();
overhead += nowMs() - start;
}
overhead /= FLAGS_overheadLoops;
// First figure out approximately how many loops of bench it takes to make overhead
// negligible.
double bench_plus_overhead = 0.0;
int round = 0;
while (bench_plus_overhead < overhead) {
if (round++ == FLAGS_maxCalibrationAttempts) {
TestRunner::Log("Warning: Cannot estimate loops for %s (%s vs. %s); skipping.",
fBenchmark->getUniqueName(),
humanize(bench_plus_overhead).c_str(),
humanize(overhead).c_str());
return std::make_tuple(0, false);
}
bench_plus_overhead = time(1);
}
// Later we'll just start and stop the timer once but loop N times.
// We'll pick N to make timer overhead negligible:
//
// overhead
// ------------------------- < FLAGS_overheadGoal
// overhead + N * Bench Time
//
// where bench_plus_overhead ~=~ overhead + Bench Time.
//
// Doing some math, we get:
//
// (overhead / FLAGS_overheadGoal) - overhead
// ------------------------------------------ < N
// bench_plus_overhead - overhead)
//
// Luckily, this also works well in practice. :)
const double numer = overhead / FLAGS_overheadGoal - overhead;
const double denom = bench_plus_overhead - overhead;
int loops = (int)ceil(numer / denom);
return std::make_tuple(loops, true);
}
};
class NonRenderingBenchmarkTarget : public RasterBenchmarkTarget {
public:
NonRenderingBenchmarkTarget(Benchmark* benchmark) : RasterBenchmarkTarget(nullptr, benchmark) {}
Benchmark::Backend getBackend() const override { return Benchmark::Backend::kNonRendering; }
SurfaceManager::CpuOrGpu isCpuOrGpuBound() const override {
return SurfaceManager::CpuOrGpu::kCPU;
}
std::map<std::string, std::string> getKeyValuePairs(std::string cpuName,
std::string gpuName) const override {
if (cpuName == "") {
return std::map<std::string, std::string>();
}
return {
{"cpu_or_gpu", "CPU"},
{"cpu_or_gpu_value", cpuName},
};
}
};
std::unique_ptr<BenchmarkTarget> BenchmarkTarget::FromConfig(std::string surfaceConfig,
Benchmark* benchmark) {
if (surfaceConfig == "nonrendering") {
return std::make_unique<NonRenderingBenchmarkTarget>(benchmark);
}
std::unique_ptr<SurfaceManager> surfaceManager = SurfaceManager::FromConfig(
surfaceConfig, {benchmark->getSize().width(), benchmark->getSize().height()});
if (surfaceManager == nullptr) {
SK_ABORT("Unknown --surfaceConfig flag value: %s.", surfaceConfig.c_str());
}
return std::make_unique<RasterBenchmarkTarget>(std::move(surfaceManager), benchmark);
}