/* * 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, 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 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 getKeyValuePairs(std::string cpuName, std::string gpuName) const override { if (cpuName == "") { return std::map(); } return { {"cpu_or_gpu", "CPU"}, {"cpu_or_gpu_value", cpuName}, }; } }; std::unique_ptr BenchmarkTarget::FromConfig(std::string surfaceConfig, Benchmark* benchmark) { if (surfaceConfig == "nonrendering") { return std::make_unique(benchmark); } std::unique_ptr 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(std::move(surfaceManager), benchmark); }