bbh_shootout.cpp revision 890b826fa381643969a65792339f5274f75d5e9a
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 20static const int kNumNormalRecordings = 10; 21static const int kNumRTreeRecordings = 10; 22static const int kNumPlaybacks = 1; 23static const size_t kNumBaseBenchmarks = 3; 24static const size_t kNumTileSizes = 3; 25static const size_t kNumBbhPlaybackBenchmarks = 3; 26static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks; 27 28enum BenchmarkType { 29 kNormal_BenchmarkType = 0, 30 kRTree_BenchmarkType, 31}; 32 33struct 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. 45struct BenchmarkControl; 46 47typedef void (*BenchmarkFunction) 48 (const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*); 49 50static void benchmark_playback( 51 const BenchmarkControl&, const SkString&, SkPicture*, BenchTimer*); 52static 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 */ 60struct 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 163SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = { 164 SkISize::Make(256, 256), 165 SkISize::Make(512, 512), 166 SkISize::Make(1024, 1024), 167}; 168 169static 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 */ 181static 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 */ 204static 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 */ 265static 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 */ 279static 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 */ 300static void benchmark_loop( 301 int argc, 302 char **argv, 303 const BenchmarkControl& benchControl, 304 SkTArray<Histogram>& histogram) { 305 for (int index = 1; index < argc; ++index) { 306 BenchTimer timer; 307 SkString path(argv[index]); 308 SkAutoTUnref<SkPicture> pic(pic_from_path(path.c_str())); 309 if (NULL == pic) { 310 SkDebugf("Couldn't create picture. Ignoring path: %s\n", path.c_str()); 311 continue; 312 } 313 benchControl.fFunction(benchControl, path, pic, &timer); 314 histogram[index - 1].fPath = path; 315 histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu); 316 } 317} 318 319int tool_main(int argc, char** argv); 320int tool_main(int argc, char** argv) { 321 SkAutoGraphics ag; 322 SkString usage; 323 usage.printf("Usage: filename [filename]*\n"); 324 325 if (argc < 2) { 326 SkDebugf("%s\n", usage.c_str()); 327 return -1; 328 } 329 330 SkTArray<Histogram> histograms[kNumBenchmarks]; 331 332 for (size_t i = 0; i < kNumBenchmarks; ++i) { 333 histograms[i].reset(argc - 1); 334 benchmark_loop(argc, argv, BenchmarkControl::Make(i), histograms[i]); 335 } 336 337 // Output gnuplot readable histogram data.. 338 const char* pbTitle = "bbh_shootout_playback.dat"; 339 const char* recTitle = "bbh_shootout_record.dat"; 340 SkFILEWStream playbackOut(pbTitle); 341 SkFILEWStream recordOut(recTitle); 342 recordOut.writeText("# "); 343 playbackOut.writeText("# "); 344 for (size_t i = 0; i < kNumBenchmarks; ++i) { 345 SkString out; 346 out.printf("%s ", BenchmarkControl::GetBenchmarkName(i).c_str()); 347 if (BenchmarkControl::GetBenchmarkFunc(i) == &benchmark_recording) { 348 recordOut.writeText(out.c_str()); 349 } 350 if (BenchmarkControl::GetBenchmarkFunc(i) == &benchmark_playback) { 351 playbackOut.writeText(out.c_str()); 352 } 353 } 354 recordOut.writeText("\n"); 355 playbackOut.writeText("\n"); 356 // Write to file, and save recording averages. 357 SkScalar avgRecord = SkIntToScalar(0); 358 SkScalar avgPlayback = SkIntToScalar(0); 359 SkScalar avgRecordRTree = SkIntToScalar(0); 360 SkScalar avgPlaybackRTree = SkIntToScalar(0); 361 for (int i = 0; i < argc - 1; ++i) { 362 SkString pbLine; 363 SkString recLine; 364 // ==== Write record info 365 recLine.printf("%d ", i); 366 SkScalar cpuTime = histograms[BenchmarkControl::kNormalRecord][i].fCpuTime; 367 recLine.appendf("%f ", cpuTime); 368 avgRecord += cpuTime; 369 cpuTime = histograms[BenchmarkControl::kRTreeRecord][i].fCpuTime; 370 recLine.appendf("%f", cpuTime); 371 avgRecordRTree += cpuTime; 372 avgPlaybackRTree += cpuTime; 373 374 // ==== Write playback info 375 pbLine.printf("%d ", i); 376 pbLine.appendf("%f ", histograms[2][i].fCpuTime); // Start with normal playback time. 377 avgPlayback += histograms[kNumBbhPlaybackBenchmarks - 1][i].fCpuTime; 378 avgPlaybackRTree += histograms[kNumBbhPlaybackBenchmarks][i].fCpuTime; 379 // Append all playback benchmark times. 380 for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) { 381 pbLine.appendf("%f ", histograms[j][i].fCpuTime); 382 } 383 pbLine.remove(pbLine.size() - 1, 1); // Remove trailing space from line. 384 pbLine.appendf("\n"); 385 recLine.appendf("\n"); 386 playbackOut.writeText(pbLine.c_str()); 387 recordOut.writeText(recLine.c_str()); 388 } 389 avgRecord /= argc - 1; 390 avgRecordRTree /= argc - 1; 391 avgPlayback /= argc - 1; 392 avgPlaybackRTree /= argc - 1; 393 SkDebugf("Average base recording time: %.3fms\n", avgRecord); 394 SkDebugf("Average recording time with rtree: %.3fms\n", avgRecordRTree); 395 SkDebugf("Average base playback time: %.3fms\n", avgPlayback); 396 SkDebugf("Average playback time with rtree: %.3fms\n", avgPlaybackRTree); 397 398 SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle); 399 400 return 0; 401} 402 403#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL) 404int main(int argc, char** argv) { 405 return tool_main(argc, argv); 406} 407#endif 408