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