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