Home | History | Annotate | Download | only in bench
      1 /*
      2  * Copyright 2014 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include <ctype.h>
      9 
     10 #include "Benchmark.h"
     11 #include "CrashHandler.h"
     12 #include "GMBench.h"
     13 #include "ProcStats.h"
     14 #include "ResultsWriter.h"
     15 #include "RecordingBench.h"
     16 #include "SKPBench.h"
     17 #include "Stats.h"
     18 #include "Timer.h"
     19 
     20 #include "SkBBHFactory.h"
     21 #include "SkCanvas.h"
     22 #include "SkCommonFlags.h"
     23 #include "SkForceLinking.h"
     24 #include "SkGraphics.h"
     25 #include "SkOSFile.h"
     26 #include "SkPictureRecorder.h"
     27 #include "SkString.h"
     28 #include "SkSurface.h"
     29 
     30 #if SK_SUPPORT_GPU
     31     #include "gl/GrGLDefines.h"
     32     #include "GrContextFactory.h"
     33     SkAutoTDelete<GrContextFactory> gGrFactory;
     34 #endif
     35 
     36 __SK_FORCE_IMAGE_DECODER_LINKING;
     37 
     38 static const int kAutoTuneLoops = -1;
     39 
     40 static const int kDefaultLoops =
     41 #ifdef SK_DEBUG
     42     1;
     43 #else
     44     kAutoTuneLoops;
     45 #endif
     46 
     47 static SkString loops_help_txt() {
     48     SkString help;
     49     help.printf("Number of times to run each bench. Set this to %d to auto-"
     50                 "tune for each bench. Timings are only reported when auto-tuning.",
     51                 kAutoTuneLoops);
     52     return help;
     53 }
     54 
     55 DEFINE_int32(loops, kDefaultLoops, loops_help_txt().c_str());
     56 
     57 DEFINE_int32(samples, 10, "Number of samples to measure for each bench.");
     58 DEFINE_int32(overheadLoops, 100000, "Loops to estimate timer overhead.");
     59 DEFINE_double(overheadGoal, 0.0001,
     60               "Loop until timer overhead is at most this fraction of our measurments.");
     61 DEFINE_double(gpuMs, 5, "Target bench time in millseconds for GPU.");
     62 DEFINE_int32(gpuFrameLag, 5, "Overestimate of maximum number of frames GPU allows to lag.");
     63 DEFINE_bool(gpuCompressAlphaMasks, false, "Compress masks generated from falling back to "
     64                                           "software path rendering.");
     65 
     66 DEFINE_string(outResultsFile, "", "If given, write results here as JSON.");
     67 DEFINE_int32(maxCalibrationAttempts, 3,
     68              "Try up to this many times to guess loops for a bench, or skip the bench.");
     69 DEFINE_int32(maxLoops, 1000000, "Never run a bench more times than this.");
     70 DEFINE_string(clip, "0,0,1000,1000", "Clip for SKPs.");
     71 DEFINE_string(scales, "1.0", "Space-separated scales for SKPs.");
     72 DEFINE_bool(bbh, true, "Build a BBH for SKPs?");
     73 
     74 static SkString humanize(double ms) {
     75     if (FLAGS_verbose) return SkStringPrintf("%llu", (uint64_t)(ms*1e6));
     76     if (ms > 1e+3)     return SkStringPrintf("%.3gs",  ms/1e3);
     77     if (ms < 1e-3)     return SkStringPrintf("%.3gns", ms*1e6);
     78 #ifdef SK_BUILD_FOR_WIN
     79     if (ms < 1)        return SkStringPrintf("%.3gus", ms*1e3);
     80 #else
     81     if (ms < 1)        return SkStringPrintf("%.3gs", ms*1e3);
     82 #endif
     83     return SkStringPrintf("%.3gms", ms);
     84 }
     85 #define HUMANIZE(ms) humanize(ms).c_str()
     86 
     87 static double time(int loops, Benchmark* bench, SkCanvas* canvas, SkGLContextHelper* gl) {
     88     if (canvas) {
     89         canvas->clear(SK_ColorWHITE);
     90     }
     91     WallTimer timer;
     92     timer.start();
     93     if (bench) {
     94         bench->draw(loops, canvas);
     95     }
     96     if (canvas) {
     97         canvas->flush();
     98     }
     99 #if SK_SUPPORT_GPU
    100     if (gl) {
    101         SK_GL(*gl, Flush());
    102         gl->swapBuffers();
    103     }
    104 #endif
    105     timer.end();
    106     return timer.fWall;
    107 }
    108 
    109 static double estimate_timer_overhead() {
    110     double overhead = 0;
    111     for (int i = 0; i < FLAGS_overheadLoops; i++) {
    112         overhead += time(1, NULL, NULL, NULL);
    113     }
    114     return overhead / FLAGS_overheadLoops;
    115 }
    116 
    117 static int clamp_loops(int loops) {
    118     if (loops < 1) {
    119         SkDebugf("ERROR: clamping loops from %d to 1.\n", loops);
    120         return 1;
    121     }
    122     if (loops > FLAGS_maxLoops) {
    123         SkDebugf("WARNING: clamping loops from %d to FLAGS_maxLoops, %d.\n", loops, FLAGS_maxLoops);
    124         return FLAGS_maxLoops;
    125     }
    126     return loops;
    127 }
    128 
    129 static bool write_canvas_png(SkCanvas* canvas, const SkString& filename) {
    130     if (filename.isEmpty()) {
    131         return false;
    132     }
    133     if (kUnknown_SkColorType == canvas->imageInfo().colorType()) {
    134         return false;
    135     }
    136     SkBitmap bmp;
    137     bmp.setInfo(canvas->imageInfo());
    138     if (!canvas->readPixels(&bmp, 0, 0)) {
    139         SkDebugf("Can't read canvas pixels.\n");
    140         return false;
    141     }
    142     SkString dir = SkOSPath::Dirname(filename.c_str());
    143     if (!sk_mkdir(dir.c_str())) {
    144         SkDebugf("Can't make dir %s.\n", dir.c_str());
    145         return false;
    146     }
    147     SkFILEWStream stream(filename.c_str());
    148     if (!stream.isValid()) {
    149         SkDebugf("Can't write %s.\n", filename.c_str());
    150         return false;
    151     }
    152     if (!SkImageEncoder::EncodeStream(&stream, bmp, SkImageEncoder::kPNG_Type, 100)) {
    153         SkDebugf("Can't encode a PNG.\n");
    154         return false;
    155     }
    156     return true;
    157 }
    158 
    159 static int kFailedLoops = -2;
    160 static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, double* samples) {
    161     // First figure out approximately how many loops of bench it takes to make overhead negligible.
    162     double bench_plus_overhead = 0.0;
    163     int round = 0;
    164     if (kAutoTuneLoops == FLAGS_loops) {
    165         while (bench_plus_overhead < overhead) {
    166             if (round++ == FLAGS_maxCalibrationAttempts) {
    167                 SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n",
    168                          bench->getUniqueName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead));
    169                 return kFailedLoops;
    170             }
    171             bench_plus_overhead = time(1, bench, canvas, NULL);
    172         }
    173     }
    174 
    175     // Later we'll just start and stop the timer once but loop N times.
    176     // We'll pick N to make timer overhead negligible:
    177     //
    178     //          overhead
    179     //  -------------------------  < FLAGS_overheadGoal
    180     //  overhead + N * Bench Time
    181     //
    182     // where bench_plus_overhead  overhead + Bench Time.
    183     //
    184     // Doing some math, we get:
    185     //
    186     //  (overhead / FLAGS_overheadGoal) - overhead
    187     //  ------------------------------------------  < N
    188     //       bench_plus_overhead - overhead)
    189     //
    190     // Luckily, this also works well in practice. :)
    191     int loops = FLAGS_loops;
    192     if (kAutoTuneLoops == loops) {
    193         const double numer = overhead / FLAGS_overheadGoal - overhead;
    194         const double denom = bench_plus_overhead - overhead;
    195         loops = (int)ceil(numer / denom);
    196     }
    197     loops = clamp_loops(loops);
    198 
    199     for (int i = 0; i < FLAGS_samples; i++) {
    200         samples[i] = time(loops, bench, canvas, NULL) / loops;
    201     }
    202     return loops;
    203 }
    204 
    205 #if SK_SUPPORT_GPU
    206 static int gpu_bench(SkGLContextHelper* gl,
    207                      Benchmark* bench,
    208                      SkCanvas* canvas,
    209                      double* samples) {
    210     gl->makeCurrent();
    211     // Make sure we're done with whatever came before.
    212     SK_GL(*gl, Finish());
    213 
    214     // First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs.
    215     int loops = FLAGS_loops;
    216     if (kAutoTuneLoops == loops) {
    217         loops = 1;
    218         double elapsed = 0;
    219         do {
    220             loops *= 2;
    221             // If the GPU lets frames lag at all, we need to make sure we're timing
    222             // _this_ round, not still timing last round.  We force this by looping
    223             // more times than any reasonable GPU will allow frames to lag.
    224             for (int i = 0; i < FLAGS_gpuFrameLag; i++) {
    225                 elapsed = time(loops, bench, canvas, gl);
    226             }
    227         } while (elapsed < FLAGS_gpuMs);
    228 
    229         // We've overshot at least a little.  Scale back linearly.
    230         loops = (int)ceil(loops * FLAGS_gpuMs / elapsed);
    231 
    232         // Might as well make sure we're not still timing our calibration.
    233         SK_GL(*gl, Finish());
    234     }
    235     loops = clamp_loops(loops);
    236 
    237     // Pretty much the same deal as the calibration: do some warmup to make
    238     // sure we're timing steady-state pipelined frames.
    239     for (int i = 0; i < FLAGS_gpuFrameLag; i++) {
    240         time(loops, bench, canvas, gl);
    241     }
    242 
    243     // Now, actually do the timing!
    244     for (int i = 0; i < FLAGS_samples; i++) {
    245         samples[i] = time(loops, bench, canvas, gl) / loops;
    246     }
    247     return loops;
    248 }
    249 #endif
    250 
    251 static SkString to_lower(const char* str) {
    252     SkString lower(str);
    253     for (size_t i = 0; i < lower.size(); i++) {
    254         lower[i] = tolower(lower[i]);
    255     }
    256     return lower;
    257 }
    258 
    259 struct Config {
    260     const char* name;
    261     Benchmark::Backend backend;
    262     SkColorType color;
    263     SkAlphaType alpha;
    264     int samples;
    265 #if SK_SUPPORT_GPU
    266     GrContextFactory::GLContextType ctxType;
    267 #else
    268     int bogusInt;
    269 #endif
    270 };
    271 
    272 struct Target {
    273     explicit Target(const Config& c) : config(c) {}
    274     const Config config;
    275     SkAutoTDelete<SkSurface> surface;
    276 #if SK_SUPPORT_GPU
    277     SkGLContextHelper* gl;
    278 #endif
    279 };
    280 
    281 static bool is_cpu_config_allowed(const char* name) {
    282     for (int i = 0; i < FLAGS_config.count(); i++) {
    283         if (to_lower(FLAGS_config[i]).equals(name)) {
    284             return true;
    285         }
    286     }
    287     return false;
    288 }
    289 
    290 #if SK_SUPPORT_GPU
    291 static bool is_gpu_config_allowed(const char* name, GrContextFactory::GLContextType ctxType,
    292                                   int sampleCnt) {
    293     if (!is_cpu_config_allowed(name)) {
    294         return false;
    295     }
    296     if (const GrContext* ctx = gGrFactory->get(ctxType)) {
    297         return sampleCnt <= ctx->getMaxSampleCount();
    298     }
    299     return false;
    300 }
    301 #endif
    302 
    303 #if SK_SUPPORT_GPU
    304 #define kBogusGLContextType GrContextFactory::kNative_GLContextType
    305 #else
    306 #define kBogusGLContextType 0
    307 #endif
    308 
    309 // Append all configs that are enabled and supported.
    310 static void create_configs(SkTDArray<Config>* configs) {
    311     #define CPU_CONFIG(name, backend, color, alpha)                                               \
    312         if (is_cpu_config_allowed(#name)) {                                                       \
    313             Config config = { #name, Benchmark::backend, color, alpha, 0, kBogusGLContextType };  \
    314             configs->push(config);                                                                \
    315         }
    316 
    317     if (FLAGS_cpu) {
    318         CPU_CONFIG(nonrendering, kNonRendering_Backend, kUnknown_SkColorType, kUnpremul_SkAlphaType)
    319         CPU_CONFIG(8888, kRaster_Backend, kN32_SkColorType, kPremul_SkAlphaType)
    320         CPU_CONFIG(565, kRaster_Backend, kRGB_565_SkColorType, kOpaque_SkAlphaType)
    321     }
    322 
    323 #if SK_SUPPORT_GPU
    324     #define GPU_CONFIG(name, ctxType, samples)                                   \
    325         if (is_gpu_config_allowed(#name, GrContextFactory::ctxType, samples)) {  \
    326             Config config = {                                                    \
    327                 #name,                                                           \
    328                 Benchmark::kGPU_Backend,                                         \
    329                 kN32_SkColorType,                                                \
    330                 kPremul_SkAlphaType,                                             \
    331                 samples,                                                         \
    332                 GrContextFactory::ctxType };                                     \
    333             configs->push(config);                                               \
    334         }
    335 
    336     if (FLAGS_gpu) {
    337         GPU_CONFIG(gpu, kNative_GLContextType, 0)
    338         GPU_CONFIG(msaa4, kNative_GLContextType, 4)
    339         GPU_CONFIG(msaa16, kNative_GLContextType, 16)
    340         GPU_CONFIG(nvprmsaa4, kNVPR_GLContextType, 4)
    341         GPU_CONFIG(nvprmsaa16, kNVPR_GLContextType, 16)
    342         GPU_CONFIG(debug, kDebug_GLContextType, 0)
    343         GPU_CONFIG(nullgpu, kNull_GLContextType, 0)
    344 #ifdef SK_ANGLE
    345         GPU_CONFIG(angle, kANGLE_GLContextType, 0)
    346 #endif
    347     }
    348 #endif
    349 }
    350 
    351 // If bench is enabled for config, returns a Target* for it, otherwise NULL.
    352 static Target* is_enabled(Benchmark* bench, const Config& config) {
    353     if (!bench->isSuitableFor(config.backend)) {
    354         return NULL;
    355     }
    356 
    357     SkImageInfo info = SkImageInfo::Make(bench->getSize().fX, bench->getSize().fY,
    358                                          config.color, config.alpha);
    359 
    360     Target* target = new Target(config);
    361 
    362     if (Benchmark::kRaster_Backend == config.backend) {
    363         target->surface.reset(SkSurface::NewRaster(info));
    364     }
    365 #if SK_SUPPORT_GPU
    366     else if (Benchmark::kGPU_Backend == config.backend) {
    367         target->surface.reset(SkSurface::NewRenderTarget(gGrFactory->get(config.ctxType), info,
    368                                                          config.samples));
    369         target->gl = gGrFactory->getGLContext(config.ctxType);
    370     }
    371 #endif
    372 
    373     if (Benchmark::kNonRendering_Backend != config.backend && !target->surface.get()) {
    374         delete target;
    375         return NULL;
    376     }
    377     return target;
    378 }
    379 
    380 // Creates targets for a benchmark and a set of configs.
    381 static void create_targets(SkTDArray<Target*>* targets, Benchmark* b,
    382                            const SkTDArray<Config>& configs) {
    383     for (int i = 0; i < configs.count(); ++i) {
    384         if (Target* t = is_enabled(b, configs[i])) {
    385             targets->push(t);
    386         }
    387 
    388     }
    389 }
    390 
    391 #if SK_SUPPORT_GPU
    392 static void fill_gpu_options(ResultsWriter* log, SkGLContextHelper* ctx) {
    393     const GrGLubyte* version;
    394     SK_GL_RET(*ctx, version, GetString(GR_GL_VERSION));
    395     log->configOption("GL_VERSION", (const char*)(version));
    396 
    397     SK_GL_RET(*ctx, version, GetString(GR_GL_RENDERER));
    398     log->configOption("GL_RENDERER", (const char*) version);
    399 
    400     SK_GL_RET(*ctx, version, GetString(GR_GL_VENDOR));
    401     log->configOption("GL_VENDOR", (const char*) version);
    402 
    403     SK_GL_RET(*ctx, version, GetString(GR_GL_SHADING_LANGUAGE_VERSION));
    404     log->configOption("GL_SHADING_LANGUAGE_VERSION", (const char*) version);
    405 }
    406 #endif
    407 
    408 class BenchmarkStream {
    409 public:
    410     BenchmarkStream() : fBenches(BenchRegistry::Head())
    411                       , fGMs(skiagm::GMRegistry::Head())
    412                       , fCurrentRecording(0)
    413                       , fCurrentScale(0)
    414                       , fCurrentSKP(0) {
    415         for (int i = 0; i < FLAGS_skps.count(); i++) {
    416             if (SkStrEndsWith(FLAGS_skps[i], ".skp")) {
    417                 fSKPs.push_back() = FLAGS_skps[i];
    418             } else {
    419                 SkOSFile::Iter it(FLAGS_skps[i], ".skp");
    420                 SkString path;
    421                 while (it.next(&path)) {
    422                     fSKPs.push_back() = SkOSPath::Join(FLAGS_skps[0], path.c_str());
    423                 }
    424             }
    425         }
    426 
    427         if (4 != sscanf(FLAGS_clip[0], "%d,%d,%d,%d",
    428                         &fClip.fLeft, &fClip.fTop, &fClip.fRight, &fClip.fBottom)) {
    429             SkDebugf("Can't parse %s from --clip as an SkIRect.\n", FLAGS_clip[0]);
    430             exit(1);
    431         }
    432 
    433         for (int i = 0; i < FLAGS_scales.count(); i++) {
    434             if (1 != sscanf(FLAGS_scales[i], "%f", &fScales.push_back())) {
    435                 SkDebugf("Can't parse %s from --scales as an SkScalar.\n", FLAGS_scales[i]);
    436                 exit(1);
    437             }
    438         }
    439     }
    440 
    441     static bool ReadPicture(const char* path, SkAutoTUnref<SkPicture>* pic) {
    442         // Not strictly necessary, as it will be checked again later,
    443         // but helps to avoid a lot of pointless work if we're going to skip it.
    444         if (SkCommandLineFlags::ShouldSkip(FLAGS_match, path)) {
    445             return false;
    446         }
    447 
    448         SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
    449         if (stream.get() == NULL) {
    450             SkDebugf("Could not read %s.\n", path);
    451             return false;
    452         }
    453 
    454         pic->reset(SkPicture::CreateFromStream(stream.get()));
    455         if (pic->get() == NULL) {
    456             SkDebugf("Could not read %s as an SkPicture.\n", path);
    457             return false;
    458         }
    459         return true;
    460     }
    461 
    462     Benchmark* next() {
    463         if (fBenches) {
    464             Benchmark* bench = fBenches->factory()(NULL);
    465             fBenches = fBenches->next();
    466             fSourceType = "bench";
    467             fBenchType  = "micro";
    468             return bench;
    469         }
    470 
    471         while (fGMs) {
    472             SkAutoTDelete<skiagm::GM> gm(fGMs->factory()(NULL));
    473             fGMs = fGMs->next();
    474             if (gm->getFlags() & skiagm::GM::kAsBench_Flag) {
    475                 fSourceType = "gm";
    476                 fBenchType  = "micro";
    477                 return SkNEW_ARGS(GMBench, (gm.detach()));
    478             }
    479         }
    480 
    481         // First add all .skps as RecordingBenches.
    482         while (fCurrentRecording < fSKPs.count()) {
    483             const SkString& path = fSKPs[fCurrentRecording++];
    484             SkAutoTUnref<SkPicture> pic;
    485             if (!ReadPicture(path.c_str(), &pic)) {
    486                 continue;
    487             }
    488             SkString name = SkOSPath::Basename(path.c_str());
    489             fSourceType = "skp";
    490             fBenchType  = "recording";
    491             return SkNEW_ARGS(RecordingBench, (name.c_str(), pic.get(), FLAGS_bbh));
    492         }
    493 
    494         // Then once each for each scale as SKPBenches (playback).
    495         while (fCurrentScale < fScales.count()) {
    496             while (fCurrentSKP < fSKPs.count()) {
    497                 const SkString& path = fSKPs[fCurrentSKP++];
    498                 SkAutoTUnref<SkPicture> pic;
    499                 if (!ReadPicture(path.c_str(), &pic)) {
    500                     continue;
    501                 }
    502                 if (FLAGS_bbh) {
    503                     // The SKP we read off disk doesn't have a BBH.  Re-record so it grows one.
    504                     // Here we use an SkTileGrid with parameters optimized for FLAGS_clip.
    505                     const SkTileGridFactory::TileGridInfo info = {
    506                         SkISize::Make(fClip.width(), fClip.height()),  // tile interval
    507                         SkISize::Make(0,0),                            // margin
    508                         SkIPoint::Make(fClip.left(), fClip.top()),     // offset
    509                     };
    510                     SkTileGridFactory factory(info);
    511                     SkPictureRecorder recorder;
    512                     pic->playback(recorder.beginRecording(pic->cullRect().width(),
    513                                                           pic->cullRect().height(),
    514                                                           &factory));
    515                     pic.reset(recorder.endRecording());
    516                 }
    517                 SkString name = SkOSPath::Basename(path.c_str());
    518                 fSourceType = "skp";
    519                 fBenchType  = "playback";
    520                 return SkNEW_ARGS(SKPBench,
    521                         (name.c_str(), pic.get(), fClip, fScales[fCurrentScale]));
    522             }
    523             fCurrentSKP = 0;
    524             fCurrentScale++;
    525         }
    526 
    527         return NULL;
    528     }
    529 
    530     void fillCurrentOptions(ResultsWriter* log) const {
    531         log->configOption("source_type", fSourceType);
    532         log->configOption("bench_type",  fBenchType);
    533         if (0 == strcmp(fSourceType, "skp")) {
    534             log->configOption("clip",
    535                     SkStringPrintf("%d %d %d %d", fClip.fLeft, fClip.fTop,
    536                                                   fClip.fRight, fClip.fBottom).c_str());
    537             log->configOption("scale", SkStringPrintf("%.2g", fScales[fCurrentScale]).c_str());
    538         }
    539     }
    540 
    541 private:
    542     const BenchRegistry* fBenches;
    543     const skiagm::GMRegistry* fGMs;
    544     SkIRect            fClip;
    545     SkTArray<SkScalar> fScales;
    546     SkTArray<SkString> fSKPs;
    547 
    548     const char* fSourceType;  // What we're benching: bench, GM, SKP, ...
    549     const char* fBenchType;   // How we bench it: micro, recording, playback, ...
    550     int fCurrentRecording;
    551     int fCurrentScale;
    552     int fCurrentSKP;
    553 };
    554 
    555 int nanobench_main();
    556 int nanobench_main() {
    557     SetupCrashHandler();
    558     SkAutoGraphics ag;
    559 
    560 #if SK_SUPPORT_GPU
    561     GrContext::Options grContextOpts;
    562     grContextOpts.fDrawPathToCompressedTexture = FLAGS_gpuCompressAlphaMasks;
    563     gGrFactory.reset(SkNEW_ARGS(GrContextFactory, (grContextOpts)));
    564 #endif
    565 
    566     if (kAutoTuneLoops != FLAGS_loops) {
    567         FLAGS_samples     = 1;
    568         FLAGS_gpuFrameLag = 0;
    569     }
    570 
    571     if (!FLAGS_writePath.isEmpty()) {
    572         SkDebugf("Writing files to %s.\n", FLAGS_writePath[0]);
    573         if (!sk_mkdir(FLAGS_writePath[0])) {
    574             SkDebugf("Could not create %s. Files won't be written.\n", FLAGS_writePath[0]);
    575             FLAGS_writePath.set(0, NULL);
    576         }
    577     }
    578 
    579     SkAutoTDelete<ResultsWriter> log(SkNEW(ResultsWriter));
    580     if (!FLAGS_outResultsFile.isEmpty()) {
    581         log.reset(SkNEW(NanoJSONResultsWriter(FLAGS_outResultsFile[0])));
    582     }
    583 
    584     if (1 == FLAGS_properties.count() % 2) {
    585         SkDebugf("ERROR: --properties must be passed with an even number of arguments.\n");
    586         return 1;
    587     }
    588     for (int i = 1; i < FLAGS_properties.count(); i += 2) {
    589         log->property(FLAGS_properties[i-1], FLAGS_properties[i]);
    590     }
    591 
    592     if (1 == FLAGS_key.count() % 2) {
    593         SkDebugf("ERROR: --key must be passed with an even number of arguments.\n");
    594         return 1;
    595     }
    596     for (int i = 1; i < FLAGS_key.count(); i += 2) {
    597         log->key(FLAGS_key[i-1], FLAGS_key[i]);
    598     }
    599 
    600     const double overhead = estimate_timer_overhead();
    601     SkDebugf("Timer overhead: %s\n", HUMANIZE(overhead));
    602 
    603     SkAutoTMalloc<double> samples(FLAGS_samples);
    604 
    605     if (kAutoTuneLoops != FLAGS_loops) {
    606         SkDebugf("Fixed number of loops; times would only be misleading so we won't print them.\n");
    607     } else if (FLAGS_verbose) {
    608         // No header.
    609     } else if (FLAGS_quiet) {
    610         SkDebugf("median\tbench\tconfig\n");
    611     } else {
    612         SkDebugf("maxrss\tloops\tmin\tmedian\tmean\tmax\tstddev\t%-*s\tconfig\tbench\n",
    613                  FLAGS_samples, "samples");
    614     }
    615 
    616     SkTDArray<Config> configs;
    617     create_configs(&configs);
    618 
    619     BenchmarkStream benchStream;
    620     while (Benchmark* b = benchStream.next()) {
    621         SkAutoTDelete<Benchmark> bench(b);
    622         if (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName())) {
    623             continue;
    624         }
    625 
    626         SkTDArray<Target*> targets;
    627         create_targets(&targets, bench.get(), configs);
    628 
    629         if (!targets.isEmpty()) {
    630             log->bench(bench->getUniqueName(), bench->getSize().fX, bench->getSize().fY);
    631             bench->preDraw();
    632         }
    633         for (int j = 0; j < targets.count(); j++) {
    634             SkCanvas* canvas = targets[j]->surface.get() ? targets[j]->surface->getCanvas() : NULL;
    635             const char* config = targets[j]->config.name;
    636 
    637             const int loops =
    638 #if SK_SUPPORT_GPU
    639                 Benchmark::kGPU_Backend == targets[j]->config.backend
    640                 ? gpu_bench(targets[j]->gl, bench.get(), canvas, samples.get())
    641                 :
    642 #endif
    643                  cpu_bench(       overhead, bench.get(), canvas, samples.get());
    644 
    645             if (canvas && !FLAGS_writePath.isEmpty() && FLAGS_writePath[0]) {
    646                 SkString pngFilename = SkOSPath::Join(FLAGS_writePath[0], config);
    647                 pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getUniqueName());
    648                 pngFilename.append(".png");
    649                 write_canvas_png(canvas, pngFilename);
    650             }
    651 
    652             if (kFailedLoops == loops) {
    653                 // Can't be timed.  A warning note has already been printed.
    654                 continue;
    655             }
    656 
    657             Stats stats(samples.get(), FLAGS_samples);
    658             log->config(config);
    659             log->configOption("name", bench->getName());
    660             benchStream.fillCurrentOptions(log.get());
    661 #if SK_SUPPORT_GPU
    662             if (Benchmark::kGPU_Backend == targets[j]->config.backend) {
    663                 fill_gpu_options(log.get(), targets[j]->gl);
    664             }
    665 #endif
    666             log->timer("min_ms",    stats.min);
    667             log->timer("median_ms", stats.median);
    668             log->timer("mean_ms",   stats.mean);
    669             log->timer("max_ms",    stats.max);
    670             log->timer("stddev_ms", sqrt(stats.var));
    671 
    672             if (kAutoTuneLoops != FLAGS_loops) {
    673                 if (targets.count() == 1) {
    674                     config = ""; // Only print the config if we run the same bench on more than one.
    675                 }
    676                 SkDebugf("%4dM\t%s\t%s\n"
    677                          , sk_tools::getMaxResidentSetSizeMB()
    678                          , bench->getUniqueName()
    679                          , config);
    680             } else if (FLAGS_verbose) {
    681                 for (int i = 0; i < FLAGS_samples; i++) {
    682                     SkDebugf("%s  ", HUMANIZE(samples[i]));
    683                 }
    684                 SkDebugf("%s\n", bench->getUniqueName());
    685             } else if (FLAGS_quiet) {
    686                 if (targets.count() == 1) {
    687                     config = ""; // Only print the config if we run the same bench on more than one.
    688                 }
    689                 SkDebugf("%s\t%s\t%s\n", HUMANIZE(stats.median), bench->getUniqueName(), config);
    690             } else {
    691                 const double stddev_percent = 100 * sqrt(stats.var) / stats.mean;
    692                 SkDebugf("%4dM\t%d\t%s\t%s\t%s\t%s\t%.0f%%\t%s\t%s\t%s\n"
    693                         , sk_tools::getMaxResidentSetSizeMB()
    694                         , loops
    695                         , HUMANIZE(stats.min)
    696                         , HUMANIZE(stats.median)
    697                         , HUMANIZE(stats.mean)
    698                         , HUMANIZE(stats.max)
    699                         , stddev_percent
    700                         , stats.plot.c_str()
    701                         , config
    702                         , bench->getUniqueName()
    703                         );
    704             }
    705         }
    706         targets.deleteAll();
    707 
    708     #if SK_SUPPORT_GPU
    709         if (FLAGS_abandonGpuContext) {
    710             gGrFactory->abandonContexts();
    711         }
    712         if (FLAGS_resetGpuContext || FLAGS_abandonGpuContext) {
    713             gGrFactory->destroyContexts();
    714         }
    715     #endif
    716     }
    717 
    718     return 0;
    719 }
    720 
    721 #if !defined SK_BUILD_FOR_IOS
    722 int main(int argc, char** argv) {
    723     SkCommandLineFlags::Parse(argc, argv);
    724     return nanobench_main();
    725 }
    726 #endif
    727