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