diff options
author | joshualitt <joshualitt@chromium.org> | 2015-10-05 07:23:30 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-05 07:23:30 -0700 |
commit | 98d2e2f095d9a5b784a32cfc2c34b30735dedcc3 (patch) | |
tree | 7aeb07fb604ad1f7647226132da6ba32c4d4e746 /tools/VisualBench | |
parent | d8982d7e923d8ab6c7a4ff9456c5ce631ae11423 (diff) |
Factor out VisualBench timing code into a helper class
BUG=skia:
Review URL: https://codereview.chromium.org/1375363003
Diffstat (limited to 'tools/VisualBench')
-rw-r--r-- | tools/VisualBench/TimingStateMachine.cpp | 122 | ||||
-rw-r--r-- | tools/VisualBench/TimingStateMachine.h | 103 | ||||
-rw-r--r-- | tools/VisualBench/VisualBenchmarkStream.cpp | 39 | ||||
-rw-r--r-- | tools/VisualBench/VisualBenchmarkStream.h | 1 | ||||
-rw-r--r-- | tools/VisualBench/VisualLightweightBenchModule.cpp | 182 | ||||
-rw-r--r-- | tools/VisualBench/VisualLightweightBenchModule.h | 48 |
6 files changed, 283 insertions, 212 deletions
diff --git a/tools/VisualBench/TimingStateMachine.cpp b/tools/VisualBench/TimingStateMachine.cpp new file mode 100644 index 0000000000..c7f2f13470 --- /dev/null +++ b/tools/VisualBench/TimingStateMachine.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "TimingStateMachine.h" + +#include "SkCanvas.h" +#include "SkCommandLineFlags.h" + +DEFINE_int32(gpuFrameLag, 5, "Overestimate of maximum number of frames GPU is allowed to lag."); +DEFINE_int32(frames, 5, "Number of frames of each skp to render per sample."); +DEFINE_double(loopMs, 5, "Each benchmark will be tuned until it takes loopsMs millseconds."); + +TimingStateMachine::TimingStateMachine() + : fCurrentFrame(0) + , fLoops(1) + , fLastMeasurement(0.) + , fState(kPreWarmLoopsPerCanvasPreDraw_State) { +} + +TimingStateMachine::ParentEvents TimingStateMachine::nextFrame(SkCanvas* canvas, + Benchmark* benchmark) { + switch (fState) { + case kPreWarmLoopsPerCanvasPreDraw_State: + return this->perCanvasPreDraw(canvas, benchmark, kPreWarmLoops_State); + case kPreWarmLoops_State: + return this->preWarm(kTuneLoops_State); + case kTuneLoops_State: + return this->tuneLoops(); + case kPreWarmTimingPerCanvasPreDraw_State: + return this->perCanvasPreDraw(canvas, benchmark, kPreWarmTiming_State); + case kPreWarmTiming_State: + return this->preWarm(kTiming_State); + case kTiming_State: + return this->timing(canvas, benchmark); + } + SkFAIL("Incomplete switch\n"); + return kTiming_ParentEvents; +} + +inline void TimingStateMachine::nextState(State nextState) { + fState = nextState; +} + +TimingStateMachine::ParentEvents TimingStateMachine::perCanvasPreDraw(SkCanvas* canvas, + Benchmark* benchmark, + State nextState) { + benchmark->perCanvasPreDraw(canvas); + benchmark->preDraw(canvas); + fCurrentFrame = 0; + this->nextState(nextState); + return kTiming_ParentEvents; +} + +TimingStateMachine::ParentEvents TimingStateMachine::preWarm(State nextState) { + if (fCurrentFrame >= FLAGS_gpuFrameLag) { + // we currently time across all frames to make sure we capture all GPU work + this->nextState(nextState); + fCurrentFrame = 0; + fTimer.start(); + } else { + fCurrentFrame++; + } + return kTiming_ParentEvents; +} + +inline double TimingStateMachine::elapsed() { + fTimer.end(); + return fTimer.fWall; +} + +void TimingStateMachine::resetTimingState() { + fCurrentFrame = 0; + fTimer = WallTimer(); +} + +inline TimingStateMachine::ParentEvents TimingStateMachine::tuneLoops() { + if (1 << 30 == fLoops) { + // We're about to wrap. Something's wrong with the bench. + SkDebugf("InnerLoops wrapped\n"); + fLoops = 1; + return kTiming_ParentEvents; + } else { + double elapsedMs = this->elapsed(); + if (elapsedMs > FLAGS_loopMs) { + this->nextState(kPreWarmTimingPerCanvasPreDraw_State); + } else { + fLoops *= 2; + this->nextState(kPreWarmLoops_State); + } + this->resetTimingState(); + return kReset_ParentEvents; + } +} + +void TimingStateMachine::recordMeasurement() { + fLastMeasurement = this->elapsed() / (FLAGS_frames * fLoops); +} + +void TimingStateMachine::nextBenchmark(SkCanvas* canvas, Benchmark* benchmark) { + benchmark->postDraw(canvas); + benchmark->perCanvasPostDraw(canvas); + fLoops = 1; + this->nextState(kPreWarmLoopsPerCanvasPreDraw_State); +} + +inline TimingStateMachine::ParentEvents TimingStateMachine::timing(SkCanvas* canvas, + Benchmark* benchmark) { + if (fCurrentFrame >= FLAGS_frames) { + this->recordMeasurement(); + this->resetTimingState(); + this->nextState(kPreWarmTimingPerCanvasPreDraw_State); + return kTimingFinished_ParentEvents; + } else { + fCurrentFrame++; + return kTiming_ParentEvents; + } +} + diff --git a/tools/VisualBench/TimingStateMachine.h b/tools/VisualBench/TimingStateMachine.h new file mode 100644 index 0000000000..69ea24337f --- /dev/null +++ b/tools/VisualBench/TimingStateMachine.h @@ -0,0 +1,103 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + */ + +#ifndef TimingStateMachine_DEFINED +#define TimingStateMachine_DEFINED + +#include "Benchmark.h" +#include "SkTArray.h" +#include "Timer.h" + +class SkCanvas; + +/* + * Manages a timer via a state machine. Can be used by modules to time benchmarks + * + * Clients call nextFrame, and must handle any requests from the timing state machine, specifically + * to reset. When kTimingFinished_ParentEvents is returned, then lastMeasurement() will return the + * timing and loops() will return the number of loops used to time. + * + * A client may continue timing the same benchmark indefinitely. To advance to the next + * benchmark, the client should call nextBenchmark. + */ +class TimingStateMachine { +public: + TimingStateMachine(); + + enum ParentEvents { + kReset_ParentEvents, + kTiming_ParentEvents, + kTimingFinished_ParentEvents,// This implies parent can read lastMeasurement() and must + // reset + }; + + ParentEvents nextFrame(SkCanvas* canvas, Benchmark* benchmark); + + /* + * The caller should call this when they are ready to move to the next benchmark. The caller + * must call this with the *last* benchmark so post draw hooks can be invoked + */ + void nextBenchmark(SkCanvas*, Benchmark*); + + + /* + * When TimingStateMachine returns kTimingFinished_ParentEvents, then the owner can call + * lastMeasurement() to get the time + */ + double lastMeasurement() const { return fLastMeasurement; } + + int loops() const { return fLoops; } + +private: + /* + * The heart of the timing state machine is an event driven timing loop. + * kPreWarmLoopsPerCanvasPreDraw_State: Before we begin timing, Benchmarks have a hook to + * access the canvas. Then we prewarm before the autotune + * loops step. + * kPreWarmLoops_State: We prewarm the gpu before auto tuning to enter a steady + * work state + * kTuneLoops_State: Then we tune the loops of the benchmark to ensure we + * are doing a measurable amount of work + * kPreWarmTimingPerCanvasPreDraw_State: Because reset the context after tuning loops to ensure + * coherent state, we need to give the benchmark + * another hook + * kPreWarmTiming_State: We prewarm the gpu again to enter a steady state + * kTiming_State: Finally we time the benchmark. When finished timing + * if we have enough samples then we'll start the next + * benchmark in the kPreWarmLoopsPerCanvasPreDraw_State. + * otherwise, we enter the + * kPreWarmTimingPerCanvasPreDraw_State for another sample + * In either case we reset the context. + */ + enum State { + kPreWarmLoopsPerCanvasPreDraw_State, + kPreWarmLoops_State, + kTuneLoops_State, + kPreWarmTimingPerCanvasPreDraw_State, + kPreWarmTiming_State, + kTiming_State, + }; + + inline void nextState(State); + ParentEvents perCanvasPreDraw(SkCanvas*, Benchmark*, State); + ParentEvents preWarm(State nextState); + inline ParentEvents tuneLoops(); + inline ParentEvents timing(SkCanvas*, Benchmark*); + inline double elapsed(); + void resetTimingState(); + void postDraw(SkCanvas*, Benchmark*); + void recordMeasurement(); + + int fCurrentFrame; + int fLoops; + double fLastMeasurement; + WallTimer fTimer; + State fState; +}; + +#endif diff --git a/tools/VisualBench/VisualBenchmarkStream.cpp b/tools/VisualBench/VisualBenchmarkStream.cpp index 9e1ce36fbc..c520eeed05 100644 --- a/tools/VisualBench/VisualBenchmarkStream.cpp +++ b/tools/VisualBench/VisualBenchmarkStream.cpp @@ -10,8 +10,10 @@ #include "CpuWrappedBenchmark.h" #include "GMBench.h" #include "SkOSFile.h" +#include "SkPath.h" #include "SkPictureRecorder.h" #include "SkStream.h" +#include "sk_tool_utils.h" #include "VisualSKPBench.h" DEFINE_bool(cpu, false, "Run in CPU mode?"); @@ -26,12 +28,41 @@ DEFINE_string2(match, m, nullptr, "it is skipped unless some list entry starts with ~"); DEFINE_string(skps, "skps", "Directory to read skps from."); +// We draw a big nonAA path to warmup the gpu / cpu +#include "SkPerlinNoiseShader.h" +class WarmupBench : public Benchmark { +public: + WarmupBench() { + sk_tool_utils::make_big_path(fPath); + } +private: + const char* onGetName() override { return "warmupbench"; } + void onDraw(int loops, SkCanvas* canvas) override { + // We draw a big path to warm up the cpu, and then use perlin noise shader to warm up the + // gpu + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(2); + + SkPaint perlinPaint; + perlinPaint.setShader(SkPerlinNoiseShader::CreateTurbulence(0.1f, 0.1f, 1, 0, + nullptr))->unref(); + SkRect rect = SkRect::MakeLTRB(0., 0., 400., 400.); + for (int i = 0; i < loops; i++) { + canvas->drawPath(fPath, paint); + canvas->drawRect(rect, perlinPaint); + } + } + SkPath fPath; +}; + VisualBenchmarkStream::VisualBenchmarkStream() : fBenches(BenchRegistry::Head()) , fGMs(skiagm::GMRegistry::Head()) , fSourceType(nullptr) , fBenchType(nullptr) - , fCurrentSKP(0) { + , fCurrentSKP(0) + , fIsWarmedUp(false) { for (int i = 0; i < FLAGS_skps.count(); i++) { if (SkStrEndsWith(FLAGS_skps[i], ".skp")) { fSKPs.push_back() = FLAGS_skps[i]; @@ -67,7 +98,13 @@ bool VisualBenchmarkStream::ReadPicture(const char* path, SkAutoTUnref<SkPicture } Benchmark* VisualBenchmarkStream::next() { + if (!fIsWarmedUp) { + fIsWarmedUp = true; + return new WarmupBench; + } + Benchmark* bench; + // skips non matching benches while ((bench = this->innerNext()) && (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName()) || diff --git a/tools/VisualBench/VisualBenchmarkStream.h b/tools/VisualBench/VisualBenchmarkStream.h index e8d2cbe524..9e5d4590cb 100644 --- a/tools/VisualBench/VisualBenchmarkStream.h +++ b/tools/VisualBench/VisualBenchmarkStream.h @@ -34,6 +34,7 @@ private: const char* fSourceType; // What we're benching: bench, GM, SKP, ... const char* fBenchType; // How we bench it: micro, playback, ... int fCurrentSKP; + bool fIsWarmedUp; }; #endif diff --git a/tools/VisualBench/VisualLightweightBenchModule.cpp b/tools/VisualBench/VisualLightweightBenchModule.cpp index cdb9318c4b..fa99caa1fb 100644 --- a/tools/VisualBench/VisualLightweightBenchModule.cpp +++ b/tools/VisualBench/VisualLightweightBenchModule.cpp @@ -25,18 +25,13 @@ __SK_FORCE_IMAGE_DECODER_LINKING; // Between samples we reset context // Between frames we swap buffers - -DEFINE_int32(maxWarmupFrames, 100, "maxmium frames to try and tune for sane timings"); -DEFINE_int32(gpuFrameLag, 5, "Overestimate of maximum number of frames GPU allows to lag."); -DEFINE_int32(samples, 10, "Number of times to time each skp."); -DEFINE_int32(frames, 5, "Number of frames of each skp to render per sample."); -DEFINE_double(loopMs, 5, "Target loop time in millseconds."); DEFINE_bool2(verbose, v, false, "enable verbose output from the test driver."); DEFINE_string(outResultsFile, "", "If given, write results here as JSON."); DEFINE_string(key, "", "Space-separated key/value pairs to add to JSON identifying this builder."); DEFINE_string(properties, "", "Space-separated key/value pairs to add to JSON identifying this run."); +DEFINE_int32(samples, 10, "Number of times to time each skp."); static SkString humanize(double ms) { if (FLAGS_verbose) { @@ -47,34 +42,8 @@ static SkString humanize(double ms) { #define HUMANIZE(time) humanize(time).c_str() -// We draw a big nonAA path to warmup the gpu / cpu -class WarmupBench : public Benchmark { -public: - WarmupBench() { - make_path(fPath); - } -private: - static void make_path(SkPath& path) { - #include "BigPathBench.inc" - } - const char* onGetName() override { return "warmupbench"; } - void onDraw(int loops, SkCanvas* canvas) override { - SkPaint paint; - paint.setStyle(SkPaint::kStroke_Style); - paint.setStrokeWidth(2); - for (int i = 0; i < loops; i++) { - canvas->drawPath(fPath, paint); - } - } - SkPath fPath; -}; - VisualLightweightBenchModule::VisualLightweightBenchModule(VisualBench* owner) : fCurrentSample(0) - , fCurrentFrame(0) - , fLoops(1) - , fState(kWarmup_State) - , fBenchmark(nullptr) , fOwner(SkRef(owner)) , fResults(new ResultsWriter) { fBenchmarkStream.reset(new VisualBenchmarkStream); @@ -106,7 +75,7 @@ VisualLightweightBenchModule::VisualLightweightBenchModule(VisualBench* owner) } inline void VisualLightweightBenchModule::renderFrame(SkCanvas* canvas) { - fBenchmark->draw(fLoops, canvas); + fBenchmark->draw(fTSM.loops(), canvas); canvas->flush(); fOwner->present(); } @@ -140,7 +109,7 @@ void VisualLightweightBenchModule::printStats() { SkDebugf("%4d/%-4dMB\t%d\t%s\t%s\t%s\t%s\t%.0f%%\t%s\t%s\n", sk_tools::getCurrResidentSetSizeMB(), sk_tools::getMaxResidentSetSizeMB(), - fLoops, + fTSM.loops(), HUMANIZE(stats.min), HUMANIZE(stats.median), HUMANIZE(stats.mean), @@ -152,12 +121,6 @@ void VisualLightweightBenchModule::printStats() { } bool VisualLightweightBenchModule::advanceRecordIfNecessary(SkCanvas* canvas) { - if (!fBenchmark && fState == kWarmup_State) { - fOwner->clear(canvas, SK_ColorWHITE, 2); - fBenchmark.reset(new WarmupBench); - return true; - } - if (fBenchmark) { return true; } @@ -178,49 +141,6 @@ bool VisualLightweightBenchModule::advanceRecordIfNecessary(SkCanvas* canvas) { return true; } -inline void VisualLightweightBenchModule::nextState(State nextState) { - fState = nextState; -} - -void VisualLightweightBenchModule::perCanvasPreDraw(SkCanvas* canvas, State nextState) { - fBenchmark->perCanvasPreDraw(canvas); - fBenchmark->preDraw(canvas); - fCurrentFrame = 0; - this->nextState(nextState); -} - -void VisualLightweightBenchModule::warmup(SkCanvas* canvas) { - if (fCurrentFrame >= FLAGS_maxWarmupFrames) { - this->nextState(kPreWarmLoopsPerCanvasPreDraw_State); - fBenchmark.reset(nullptr); - this->resetTimingState(); - fLoops = 1; - } else { - bool isEven = (fCurrentFrame++ % 2) == 0; - if (isEven) { - fTimer.start(); - } else { - double elapsedMs = this->elapsed(); - if (elapsedMs < FLAGS_loopMs) { - fLoops *= 2; - } - fTimer = WallTimer(); - fOwner->reset(); - } - } -} - -void VisualLightweightBenchModule::preWarm(State nextState) { - if (fCurrentFrame >= FLAGS_gpuFrameLag) { - // we currently time across all frames to make sure we capture all GPU work - this->nextState(nextState); - fCurrentFrame = 0; - fTimer.start(); - } else { - fCurrentFrame++; - } -} - void VisualLightweightBenchModule::draw(SkCanvas* canvas) { if (!this->advanceRecordIfNecessary(canvas)) { SkDebugf("Exiting VisualBench successfully\n"); @@ -228,93 +148,25 @@ void VisualLightweightBenchModule::draw(SkCanvas* canvas) { return; } this->renderFrame(canvas); - switch (fState) { - case kWarmup_State: { - this->warmup(canvas); - break; - } - case kPreWarmLoopsPerCanvasPreDraw_State: { - this->perCanvasPreDraw(canvas, kPreWarmLoops_State); - break; - } - case kPreWarmLoops_State: { - this->preWarm(kTuneLoops_State); - break; - } - case kTuneLoops_State: { - this->tuneLoops(); - break; - } - case kPreWarmTimingPerCanvasPreDraw_State: { - this->perCanvasPreDraw(canvas, kPreWarmTiming_State); + TimingStateMachine::ParentEvents event = fTSM.nextFrame(canvas, fBenchmark); + switch (event) { + case TimingStateMachine::kReset_ParentEvents: + fOwner->reset(); break; - } - case kPreWarmTiming_State: { - this->preWarm(kTiming_State); + case TimingStateMachine::kTiming_ParentEvents: break; - } - case kTiming_State: { - this->timing(canvas); + case TimingStateMachine::kTimingFinished_ParentEvents: + fOwner->reset(); + fRecords.back().fMeasurements.push_back(fTSM.lastMeasurement()); + if (++fCurrentSample > FLAGS_samples) { + this->printStats(); + fTSM.nextBenchmark(canvas, fBenchmark); + fCurrentSample = 0; + fBenchmark.reset(nullptr); + } break; - } - } -} - -inline double VisualLightweightBenchModule::elapsed() { - fTimer.end(); - return fTimer.fWall; -} - -void VisualLightweightBenchModule::resetTimingState() { - fCurrentFrame = 0; - fTimer = WallTimer(); - fOwner->reset(); -} - -inline void VisualLightweightBenchModule::tuneLoops() { - if (1 << 30 == fLoops) { - // We're about to wrap. Something's wrong with the bench. - SkDebugf("InnerLoops wrapped\n"); - fLoops = 1; - } else { - double elapsedMs = this->elapsed(); - if (elapsedMs > FLAGS_loopMs) { - this->nextState(kPreWarmTimingPerCanvasPreDraw_State); - } else { - fLoops *= 2; - this->nextState(kPreWarmLoops_State); - } - this->resetTimingState(); } -} -void VisualLightweightBenchModule::recordMeasurement() { - double measurement = this->elapsed() / (FLAGS_frames * fLoops); - fRecords.back().fMeasurements.push_back(measurement); -} - -void VisualLightweightBenchModule::postDraw(SkCanvas* canvas) { - fBenchmark->postDraw(canvas); - fBenchmark->perCanvasPostDraw(canvas); - fBenchmark.reset(nullptr); - fCurrentSample = 0; - fLoops = 1; -} - -inline void VisualLightweightBenchModule::timing(SkCanvas* canvas) { - if (fCurrentFrame >= FLAGS_frames) { - this->recordMeasurement(); - if (fCurrentSample++ >= FLAGS_samples) { - this->printStats(); - this->postDraw(canvas); - this->nextState(kPreWarmLoopsPerCanvasPreDraw_State); - } else { - this->nextState(kPreWarmTimingPerCanvasPreDraw_State); - } - this->resetTimingState(); - } else { - fCurrentFrame++; - } } bool VisualLightweightBenchModule::onHandleChar(SkUnichar c) { diff --git a/tools/VisualBench/VisualLightweightBenchModule.h b/tools/VisualBench/VisualLightweightBenchModule.h index 1a30875c61..ffa109dc49 100644 --- a/tools/VisualBench/VisualLightweightBenchModule.h +++ b/tools/VisualBench/VisualLightweightBenchModule.h @@ -13,7 +13,7 @@ #include "ResultsWriter.h" #include "SkPicture.h" -#include "Timer.h" +#include "TimingStateMachine.h" #include "VisualBench.h" #include "VisualBenchmarkStream.h" @@ -32,65 +32,21 @@ public: bool onHandleChar(SkUnichar c) override; private: - /* - * The heart of visual bench is an event driven timing loop. - * kWarmup_State: We run a dummy bench to let things settle on startup - * kPreWarmLoopsPerCanvasPreDraw_State: Before we begin timing, Benchmarks have a hook to - * access the canvas. Then we prewarm before the autotune - * loops step. - * kPreWarmLoops_State: We prewarm the gpu before auto tuning to enter a steady - * work state - * kTuneLoops_State: Then we tune the loops of the benchmark to ensure we - * are doing a measurable amount of work - * kPreWarmTimingPerCanvasPreDraw_State: Because reset the context after tuning loops to ensure - * coherent state, we need to give the benchmark - * another hook - * kPreWarmTiming_State: We prewarm the gpu again to enter a steady state - * kTiming_State: Finally we time the benchmark. When finished timing - * if we have enough samples then we'll start the next - * benchmark in the kPreWarmLoopsPerCanvasPreDraw_State. - * otherwise, we enter the - * kPreWarmTimingPerCanvasPreDraw_State for another sample - * In either case we reset the context. - */ - enum State { - kWarmup_State, - kPreWarmLoopsPerCanvasPreDraw_State, - kPreWarmLoops_State, - kTuneLoops_State, - kPreWarmTimingPerCanvasPreDraw_State, - kPreWarmTiming_State, - kTiming_State, - }; void setTitle(); bool setupBackend(); void setupRenderTarget(); void printStats(); bool advanceRecordIfNecessary(SkCanvas*); inline void renderFrame(SkCanvas*); - inline void nextState(State); - void perCanvasPreDraw(SkCanvas*, State); - void preWarm(State nextState); - inline void tuneLoops(); - inline void timing(SkCanvas*); - inline double elapsed(); - void resetTimingState(); - void postDraw(SkCanvas*); - void recordMeasurement(); - void warmup(SkCanvas* canvas); struct Record { SkTArray<double> fMeasurements; }; - int fCurrentSample; - int fCurrentFrame; - int fLoops; SkTArray<Record> fRecords; - WallTimer fTimer; - State fState; SkAutoTDelete<VisualBenchmarkStream> fBenchmarkStream; SkAutoTUnref<Benchmark> fBenchmark; + TimingStateMachine fTSM; // support framework SkAutoTUnref<VisualBench> fOwner; |