Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright 2013 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 "BenchTimer.h"
      9 #include "LazyDecodeBitmap.h"
     10 #include "PictureBenchmark.h"
     11 #include "PictureRenderer.h"
     12 #include "SkBenchmark.h"
     13 #include "SkForceLinking.h"
     14 #include "SkGraphics.h"
     15 #include "SkStream.h"
     16 #include "SkString.h"
     17 #include "SkTArray.h"
     18 #include "TimerData.h"
     19 
     20 static const int kNumNormalRecordings = 10;
     21 static const int kNumRTreeRecordings = 10;
     22 static const int kNumPlaybacks = 1;
     23 static const size_t kNumBaseBenchmarks = 3;
     24 static const size_t kNumTileSizes = 3;
     25 static const size_t kNumBbhPlaybackBenchmarks = 3;
     26 static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks;
     27 
     28 enum BenchmarkType {
     29     kNormal_BenchmarkType = 0,
     30     kRTree_BenchmarkType,
     31 };
     32 
     33 struct Histogram {
     34     Histogram() {
     35         // Make fCpuTime negative so that we don't mess with stats:
     36         fCpuTime = SkIntToScalar(-1);
     37     }
     38     SkScalar fCpuTime;
     39     SkString fPath;
     40 };
     41 
     42 
     43 ////////////////////////////////////////////////////////////////////////////////
     44 // Defined below.
     45 struct BenchmarkControl;
     46 
     47 typedef void (*BenchmarkFunction)
     48     (const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*);
     49 
     50 static void benchmark_playback(
     51     const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*);
     52 static void benchmark_recording(
     53     const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*);
     54 ////////////////////////////////////////////////////////////////////////////////
     55 
     56 /**
     57  * Acts as a POD containing information needed to run a benchmark.
     58  * Provides static methods to poll benchmark info from an index.
     59  */
     60 struct BenchmarkControl {
     61     SkISize fTileSize;
     62     BenchmarkType fType;
     63     BenchmarkFunction fFunction;
     64     SkString fName;
     65 
     66     /**
     67      * Will construct a BenchmarkControl instance from an index between 0 an kNumBenchmarks.
     68      */
     69     static BenchmarkControl Make(size_t i) {
     70         SkASSERT(kNumBenchmarks > i);
     71         BenchmarkControl benchControl;
     72         benchControl.fTileSize = GetTileSize(i);
     73         benchControl.fType = GetBenchmarkType(i);
     74         benchControl.fFunction = GetBenchmarkFunc(i);
     75         benchControl.fName = GetBenchmarkName(i);
     76         return benchControl;
     77     }
     78 
     79     enum BaseBenchmarks {
     80         kNormalRecord = 0,
     81         kRTreeRecord,
     82         kNormalPlayback,
     83     };
     84 
     85     static SkISize fTileSizes[kNumTileSizes];
     86 
     87     static SkISize GetTileSize(size_t i) {
     88         // Two of the base benchmarks don't need a tile size. But to maintain simplicity
     89         // down the pipeline we have to let a couple of values unused.
     90         if (i < kNumBaseBenchmarks) {
     91             return SkISize::Make(256, 256);
     92         }
     93         if (i >= kNumBaseBenchmarks && i < kNumBenchmarks) {
     94             return fTileSizes[i - kNumBaseBenchmarks];
     95         }
     96         SkASSERT(0);
     97         return SkISize::Make(0, 0);
     98     }
     99 
    100     static BenchmarkType GetBenchmarkType(size_t i) {
    101         if (i < kNumBaseBenchmarks) {
    102             switch (i) {
    103             case kNormalRecord:
    104                 return kNormal_BenchmarkType;
    105             case kNormalPlayback:
    106                 return kNormal_BenchmarkType;
    107             case kRTreeRecord:
    108                 return kRTree_BenchmarkType;
    109             }
    110         }
    111         if (i < kNumBenchmarks) {
    112             return kRTree_BenchmarkType;
    113         }
    114         SkASSERT(0);
    115         return kRTree_BenchmarkType;
    116     }
    117 
    118     static BenchmarkFunction GetBenchmarkFunc(size_t i) {
    119         // Base functions.
    120         switch (i) {
    121             case kNormalRecord:
    122                 return benchmark_recording;
    123             case kNormalPlayback:
    124                 return benchmark_playback;
    125             case kRTreeRecord:
    126                 return benchmark_recording;
    127         }
    128         // RTree playbacks
    129         if (i < kNumBenchmarks) {
    130             return benchmark_playback;
    131         }
    132         SkASSERT(0);
    133         return NULL;
    134     }
    135 
    136     static SkString GetBenchmarkName(size_t i) {
    137         // Base benchmark names
    138         switch (i) {
    139             case kNormalRecord:
    140                 return SkString("normal_recording");
    141             case kNormalPlayback:
    142                 return SkString("normal_playback");
    143             case kRTreeRecord:
    144                 return SkString("rtree_recording");
    145         }
    146         // RTree benchmark names.
    147         if (i < kNumBenchmarks) {
    148             SkASSERT(i >= kNumBaseBenchmarks);
    149             SkString name;
    150             name.printf("rtree_playback_%dx%d",
    151                     fTileSizes[i - kNumBaseBenchmarks].fWidth,
    152                     fTileSizes[i - kNumBaseBenchmarks].fHeight);
    153             return name;
    154 
    155         } else {
    156             SkASSERT(0);
    157         }
    158         return SkString("");
    159     }
    160 
    161 };
    162 
    163 SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = {
    164     SkISize::Make(256, 256),
    165     SkISize::Make(512, 512),
    166     SkISize::Make(1024, 1024),
    167 };
    168 
    169 static SkPicture* pic_from_path(const char path[]) {
    170     SkFILEStream stream(path);
    171     if (!stream.isValid()) {
    172         SkDebugf("-- Can't open '%s'\n", path);
    173         return NULL;
    174     }
    175     return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap);
    176 }
    177 
    178 /**
    179  * Returns true when a tiled renderer with no bounding box hierarchy produces the given bitmap.
    180  */
    181 static bool compare_picture(const SkString& path, const SkBitmap& inBitmap, SkPicture* pic) {
    182     SkBitmap* bitmap;
    183     sk_tools::TiledPictureRenderer renderer;
    184     renderer.setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
    185     renderer.init(pic);
    186     renderer.setup();
    187     renderer.render(&path, &bitmap);
    188     SkAutoTDelete<SkBitmap> bmDeleter(bitmap);
    189     renderer.end();
    190 
    191     if (bitmap->getSize() != inBitmap.getSize()) {
    192         return false;
    193     }
    194     return !memcmp(bitmap->getPixels(), inBitmap.getPixels(), bitmap->getSize());
    195 }
    196 
    197 /**
    198  * This function is the sink to which all work ends up going.
    199  * Renders the picture into the renderer. It may or may not use an RTree.
    200  * The renderer is chosen upstream. If we want to measure recording, we will
    201  * use a RecordPictureRenderer. If we want to measure rendering, we will use a
    202  * TiledPictureRenderer.
    203  */
    204 static void do_benchmark_work(sk_tools::PictureRenderer* renderer,
    205         int benchmarkType, const SkString& path, SkPicture* pic,
    206         const int numRepeats, const char *msg, BenchTimer* timer) {
    207     SkString msgPrefix;
    208 
    209     switch (benchmarkType){
    210         case kNormal_BenchmarkType:
    211             msgPrefix.set("Normal");
    212             renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
    213             break;
    214         case kRTree_BenchmarkType:
    215             msgPrefix.set("RTree");
    216             renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBoxHierarchyType);
    217             break;
    218         default:
    219             SkASSERT(0);
    220             break;
    221     }
    222 
    223     renderer->init(pic);
    224 
    225     /**
    226      * If the renderer is not tiled, assume we are measuring recording.
    227      */
    228     bool isPlayback = (NULL != renderer->getTiledRenderer());
    229     // Will be non-null during RTree picture playback. For correctness test.
    230     SkBitmap* bitmap = NULL;
    231 
    232     SkDebugf("%s %s %s %d times...\n", msgPrefix.c_str(), msg, path.c_str(), numRepeats);
    233     for (int i = 0; i < numRepeats; ++i) {
    234         // Set up the bitmap.
    235         SkBitmap** out = NULL;
    236         if (i == 0 && kRTree_BenchmarkType == benchmarkType && isPlayback) {
    237             out = &bitmap;
    238         }
    239 
    240         renderer->setup();
    241         // Render once to fill caches. Fill bitmap during the first iteration.
    242         renderer->render(NULL, out);
    243         // Render again to measure
    244         timer->start();
    245         bool result = renderer->render(NULL);
    246         timer->end();
    247 
    248         // We only care about a false result on playback. RecordPictureRenderer::render will always
    249         // return false because we are passing a NULL file name on purpose; which is fine.
    250         if (isPlayback && !result) {
    251             SkDebugf("Error rendering during playback.\n");
    252         }
    253     }
    254     if (bitmap) {
    255         SkAutoTDelete<SkBitmap> bmDeleter(bitmap);
    256         if (!compare_picture(path, *bitmap, pic)) {
    257             SkDebugf("Error: RTree produced different bitmap\n");
    258         }
    259     }
    260 }
    261 
    262 /**
    263  * Call do_benchmark_work with a tiled renderer using the default tile dimensions.
    264  */
    265 static void benchmark_playback(
    266         const BenchmarkControl& benchControl,
    267         const SkString& path, SkPicture* pic, BenchTimer* timer) {
    268     sk_tools::TiledPictureRenderer renderer;
    269 
    270     SkString message("tiled_playback");
    271     message.appendf("_%dx%d", benchControl.fTileSize.fWidth, benchControl.fTileSize.fHeight);
    272     do_benchmark_work(&renderer, benchControl.fType,
    273             path, pic, kNumPlaybacks, message.c_str(), timer);
    274 }
    275 
    276 /**
    277  * Call do_benchmark_work with a RecordPictureRenderer.
    278  */
    279 static void benchmark_recording(
    280         const BenchmarkControl& benchControl,
    281         const SkString& path, SkPicture* pic, BenchTimer* timer) {
    282     sk_tools::RecordPictureRenderer renderer;
    283     int numRecordings = 0;
    284     switch(benchControl.fType) {
    285         case kRTree_BenchmarkType:
    286             numRecordings = kNumRTreeRecordings;
    287             break;
    288         case kNormal_BenchmarkType:
    289             numRecordings = kNumNormalRecordings;
    290             break;
    291     }
    292     do_benchmark_work(&renderer, benchControl.fType,
    293             path, pic, numRecordings, "recording", timer);
    294 }
    295 
    296 /**
    297  * Takes argc,argv along with one of the benchmark functions defined above.
    298  * Will loop along all skp files and perform measurments.
    299  *
    300  * Returns a SkScalar representing CPU time taken during benchmark.
    301  * As a side effect, it spits the timer result to stdout.
    302  * Will return -1.0 on error.
    303  */
    304 static bool benchmark_loop(
    305         int argc,
    306         char **argv,
    307         const BenchmarkControl& benchControl,
    308         SkTArray<Histogram>& histogram) {
    309     static const SkString timeFormat("%f");
    310     TimerData timerData(argc - 1);
    311     for (int index = 1; index < argc; ++index) {
    312         BenchTimer timer;
    313         SkString path(argv[index]);
    314         SkAutoTUnref<SkPicture> pic(pic_from_path(path.c_str()));
    315         if (NULL == pic) {
    316             SkDebugf("Couldn't create picture. Ignoring path: %s\n", path.c_str());
    317             continue;
    318         }
    319         benchControl.fFunction(benchControl, path, pic, &timer);
    320 
    321         histogram[index - 1].fPath = path;
    322         histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu);
    323     }
    324 
    325     const SkString timerResult = timerData.getResult(
    326             /*doubleFormat = */ timeFormat.c_str(),
    327             /*result = */ TimerData::kAvg_Result,
    328             /*configName = */ benchControl.fName.c_str(),
    329             /*timerFlags = */ TimerData::kCpu_Flag);
    330 
    331     const char findStr[] = "= ";
    332     int pos = timerResult.find(findStr);
    333     if (-1 == pos) {
    334         SkDebugf("Unexpected output from TimerData::getResult(...). Unable to parse.\n");
    335         return false;
    336     }
    337 
    338     SkScalar cpuTime = SkDoubleToScalar(atof(timerResult.c_str() + pos + sizeof(findStr) - 1));
    339     if (cpuTime == 0) {  // atof returns 0.0 on error.
    340         SkDebugf("Unable to read value from timer result.\n");
    341         return false;
    342     }
    343     return true;
    344 }
    345 
    346 int tool_main(int argc, char** argv);
    347 int tool_main(int argc, char** argv) {
    348     SkAutoGraphics ag;
    349     SkString usage;
    350     usage.printf("Usage: filename [filename]*\n");
    351 
    352     if (argc < 2) {
    353         SkDebugf("%s\n", usage.c_str());
    354         return -1;
    355     }
    356 
    357     SkTArray<Histogram> histograms[kNumBenchmarks];
    358 
    359     for (size_t i = 0; i < kNumBenchmarks; ++i) {
    360         histograms[i].reset(argc - 1);
    361         bool success = benchmark_loop(
    362                 argc, argv,
    363                 BenchmarkControl::Make(i),
    364                 histograms[i]);
    365         if (!success) {
    366             SkDebugf("benchmark_loop failed at index %d\n", i);
    367         }
    368     }
    369 
    370     // Output gnuplot readable histogram data..
    371     const char* pbTitle = "bbh_shootout_playback.dat";
    372     const char* recTitle = "bbh_shootout_record.dat";
    373     SkFILEWStream playbackOut(pbTitle);
    374     SkFILEWStream recordOut(recTitle);
    375     recordOut.writeText("# ");
    376     playbackOut.writeText("# ");
    377     for (size_t i = 0; i < kNumBenchmarks; ++i) {
    378         SkString out;
    379         out.printf("%s ", BenchmarkControl::GetBenchmarkName(i).c_str());
    380         if (BenchmarkControl::GetBenchmarkFunc(i) == &benchmark_recording) {
    381             recordOut.writeText(out.c_str());
    382         }
    383         if (BenchmarkControl::GetBenchmarkFunc(i) == &benchmark_playback) {
    384             playbackOut.writeText(out.c_str());
    385         }
    386     }
    387     recordOut.writeText("\n");
    388     playbackOut.writeText("\n");
    389     // Write to file, and save recording averages.
    390     SkScalar avgRecord = SkIntToScalar(0);
    391     SkScalar avgPlayback = SkIntToScalar(0);
    392     SkScalar avgRecordRTree = SkIntToScalar(0);
    393     SkScalar avgPlaybackRTree = SkIntToScalar(0);
    394     for (int i = 0; i < argc - 1; ++i) {
    395         SkString pbLine;
    396         SkString recLine;
    397         // ==== Write record info
    398         recLine.printf("%d ", i);
    399         SkScalar cpuTime = histograms[BenchmarkControl::kNormalRecord][i].fCpuTime;
    400         recLine.appendf("%f ", cpuTime);
    401         avgRecord += cpuTime;
    402         cpuTime = histograms[BenchmarkControl::kRTreeRecord][i].fCpuTime;
    403         recLine.appendf("%f", cpuTime);
    404         avgRecordRTree += cpuTime;
    405         avgPlaybackRTree += cpuTime;
    406 
    407         // ==== Write playback info
    408         pbLine.printf("%d ", i);
    409         pbLine.appendf("%f ", histograms[2][i].fCpuTime);  // Start with normal playback time.
    410         avgPlayback += histograms[kNumBbhPlaybackBenchmarks - 1][i].fCpuTime;
    411         avgPlaybackRTree += histograms[kNumBbhPlaybackBenchmarks][i].fCpuTime;
    412         // Append all playback benchmark times.
    413         for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) {
    414             pbLine.appendf("%f ", histograms[j][i].fCpuTime);
    415         }
    416         pbLine.remove(pbLine.size() - 1, 1);  // Remove trailing space from line.
    417         pbLine.appendf("\n");
    418         recLine.appendf("\n");
    419         playbackOut.writeText(pbLine.c_str());
    420         recordOut.writeText(recLine.c_str());
    421     }
    422     avgRecord /= argc - 1;
    423     avgRecordRTree /= argc - 1;
    424     avgPlayback /= argc - 1;
    425     avgPlaybackRTree /= argc - 1;
    426     SkDebugf("Average base recording time: %.3fms\n", avgRecord);
    427     SkDebugf("Average recording time with rtree: %.3fms\n", avgRecordRTree);
    428     SkDebugf("Average base playback time: %.3fms\n", avgPlayback);
    429     SkDebugf("Average playback time with rtree: %.3fms\n", avgPlaybackRTree);
    430 
    431     SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle);
    432 
    433     return 0;
    434 }
    435 
    436 #if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
    437 int main(int argc, char** argv) {
    438     return tool_main(argc, argv);
    439 }
    440 #endif
    441