Chromium Code Reviews| Index: bench/nanobench.cpp |
| diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp |
| index c808183b194d57d9a1ed5ebd1e12f5b41a2bc5c5..958efe54e8b7a4b6cc50355a350a7259e9122521 100644 |
| --- a/bench/nanobench.cpp |
| +++ b/bench/nanobench.cpp |
| @@ -31,12 +31,27 @@ |
| __SK_FORCE_IMAGE_DECODER_LINKING; |
| -#if SK_DEBUG |
| - DEFINE_bool(runOnce, true, "Run each benchmark just once?"); |
| +static const int kAutoTuneLoops = -1; |
| + |
| +static const int kDefaultLoops = |
| +#ifdef SK_DEBUG |
| + 1; |
| #else |
| - DEFINE_bool(runOnce, false, "Run each benchmark just once?"); |
| + kAutoTuneLoops; |
| #endif |
| +static SkString loops_help_txt() { |
| + SkString help; |
| + help.printf("Number of times to run each bench. Set this to %d to auto-" |
| + "tune per-bench. Timings are only reported when auto-tuning.", |
| + kAutoTuneLoops); |
| + return help; |
| +} |
| + |
| +DEFINE_int32(loops, kDefaultLoops, loops_help_txt().c_str()); |
|
mtklein
2014/08/07 18:00:47
This seems somewhat unsafe unless DEFINE_int32 imm
bsalomon
2014/08/07 20:08:10
It does copy it into an SkString.
|
| + |
| +DEFINE_string2(writePath, w, "", "If set, write benches here as .pngs."); |
| + |
| DEFINE_int32(samples, 10, "Number of samples to measure for each bench."); |
| DEFINE_int32(overheadLoops, 100000, "Loops to estimate timer overhead."); |
| DEFINE_double(overheadGoal, 0.0001, |
| @@ -105,17 +120,50 @@ static int clamp_loops(int loops) { |
| return loops; |
| } |
| +static bool write_canvas_png(SkCanvas* canvas, const SkString& filename) { |
| + if (filename.isEmpty()) { |
| + return false; |
| + } |
| + if (kUnknown_SkColorType == canvas->imageInfo().fColorType) { |
| + return false; |
| + } |
| + SkBitmap bmp; |
| + bmp.setInfo(canvas->imageInfo()); |
| + if (!canvas->readPixels(&bmp, 0, 0)) { |
| + SkDebugf("Can't read canvas pixels.\n"); |
| + return false; |
| + } |
| + SkString dir = SkOSPath::Dirname(filename.c_str()); |
| + if (!sk_mkdir(dir.c_str())) { |
| + SkDebugf("Can't make dir %s.\n", dir.c_str()); |
| + return false; |
| + } |
| + SkFILEWStream stream(filename.c_str()); |
| + if (!stream.isValid()) { |
| + SkDebugf("Can't write %s.\n", filename.c_str()); |
| + return false; |
| + } |
| + if (!SkImageEncoder::EncodeStream(&stream, bmp, SkImageEncoder::kPNG_Type, 100)) { |
| + SkDebugf("Can't encode a PNG.\n"); |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| +static int kFailedLoops = -2; |
| static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, double* samples) { |
| // 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) { |
| - SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n", |
| - bench->getName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead)); |
| - return 0; |
| + if (kAutoTuneLoops == FLAGS_loops) { |
| + while (bench_plus_overhead < overhead) { |
| + if (round++ == FLAGS_maxCalibrationAttempts) { |
| + SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n", |
| + bench->getName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead)); |
| + return kFailedLoops; |
| + } |
| + bench_plus_overhead = time(1, bench, canvas, NULL); |
| } |
| - bench_plus_overhead = time(1, bench, canvas, NULL); |
| } |
| // Later we'll just start and stop the timer once but loop N times. |
| @@ -134,9 +182,13 @@ static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, |
| // 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; |
| - const int loops = clamp_loops(FLAGS_runOnce ? 1 : (int)ceil(numer / denom)); |
| + int loops = FLAGS_loops; |
| + if (kAutoTuneLoops == loops) { |
| + const double numer = overhead / FLAGS_overheadGoal - overhead; |
| + const double denom = bench_plus_overhead - overhead; |
| + loops = (int)ceil(numer / denom); |
| + } |
| + loops = clamp_loops(loops); |
| for (int i = 0; i < FLAGS_samples; i++) { |
| samples[i] = time(loops, bench, canvas, NULL) / loops; |
| @@ -154,8 +206,9 @@ static int gpu_bench(SkGLContextHelper* gl, |
| SK_GL(*gl, Finish()); |
| // First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs. |
| - int loops = 1; |
| - if (!FLAGS_runOnce) { |
| + int loops = FLAGS_loops; |
| + if (kAutoTuneLoops == loops) { |
| + loops = 1; |
| double elapsed = 0; |
| do { |
| loops *= 2; |
| @@ -474,11 +527,26 @@ int nanobench_main() { |
| SetupCrashHandler(); |
| SkAutoGraphics ag; |
| - if (FLAGS_runOnce) { |
| + if (kAutoTuneLoops != FLAGS_loops) { |
| FLAGS_samples = 1; |
| FLAGS_gpuFrameLag = 0; |
| } |
| + if (!FLAGS_writePath.isEmpty()) { |
| + SkDebugf("Writing files to %s.", FLAGS_writePath[0]); |
| + if (FLAGS_writePath.count() > 1) { |
| + SkDebugf(" Ignoring: ", FLAGS_writePath[0]); |
|
mtklein
2014/08/07 18:00:47
bogus extra arg to SkDebugf?
Maybe it's just me b
bsalomon
2014/08/07 20:08:10
Done.
|
| + for (int i = 1; i < FLAGS_writePath.count(); ++i) { |
| + SkDebugf("%s ", FLAGS_writePath[i]); |
| + } |
| + } |
| + SkDebugf("\n"); |
| + if (!sk_mkdir(FLAGS_writePath[0])) { |
|
mtklein
2014/08/07 18:00:47
Don't we do this inside write_canvas_png?
bsalomon
2014/08/07 20:08:10
It seems that sk_mkdir doesn't mkdirs recursively.
|
| + SkDebugf("Could not create %s. Files won't be written.\n", FLAGS_writePath[0]); |
| + FLAGS_writePath.set(0, NULL); |
| + } |
| + } |
| + |
| MultiResultsWriter log; |
| SkAutoTDelete<NanoJSONResultsWriter> json; |
| if (!FLAGS_outResultsFile.isEmpty()) { |
| @@ -502,8 +570,8 @@ int nanobench_main() { |
| SkAutoTMalloc<double> samples(FLAGS_samples); |
| - if (FLAGS_runOnce) { |
| - SkDebugf("--runOnce is true; times would only be misleading so we won't print them.\n"); |
| + if (kAutoTuneLoops != FLAGS_loops) { |
| + SkDebugf("Fixed number of loops; times would only be misleading so we won't print them.\n"); |
| } else if (FLAGS_verbose) { |
| // No header. |
| } else if (FLAGS_quiet) { |
| @@ -540,7 +608,13 @@ int nanobench_main() { |
| continue; |
| } |
| #endif |
| - |
| + SkString pngFilename; |
| + if (canvas && !FLAGS_writePath.isEmpty() && NULL != FLAGS_writePath[0]) { |
| + canvas->clear(SK_ColorWHITE); |
|
mtklein
2014/08/07 18:00:47
Perhaps we should always clear canvas to white? a
bsalomon
2014/08/07 20:08:10
Done.
|
| + pngFilename = SkOSPath::Join(FLAGS_writePath[0], config); |
| + pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getName()); |
| + pngFilename.append(".png"); |
| + } |
| const int loops = |
| #if SK_SUPPORT_GPU |
| Benchmark::kGPU_Backend == targets[j]->config.backend |
| @@ -549,7 +623,11 @@ int nanobench_main() { |
| #endif |
| cpu_bench( overhead, bench.get(), canvas, samples.get()); |
| - if (loops == 0) { |
| + if (!pngFilename.isEmpty()) { |
| + write_canvas_png(canvas, pngFilename); |
| + } |
| + |
| + if (kFailedLoops == loops) { |
| // Can't be timed. A warning note has already been printed. |
| continue; |
| } |
| @@ -568,7 +646,7 @@ int nanobench_main() { |
| log.timer("max_ms", stats.max); |
| log.timer("stddev_ms", sqrt(stats.var)); |
| - if (FLAGS_runOnce) { |
| + if (kAutoTuneLoops != FLAGS_loops) { |
| if (targets.count() == 1) { |
| config = ""; // Only print the config if we run the same bench on more than one. |
| } |