OLD | NEW |
(Empty) | |
| 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 |
| 19 static const int kNumNormalRecordings = SkBENCHLOOP(10); |
| 20 static const int kNumRTreeRecordings = SkBENCHLOOP(10); |
| 21 static const int kNumPlaybacks = SkBENCHLOOP(4); |
| 22 static const size_t kNumBaseBenchmarks = 3; |
| 23 static const size_t kNumTileSizes = 3; |
| 24 static const size_t kNumBbhPlaybackBenchmarks = 3; |
| 25 static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchma
rks; |
| 26 |
| 27 enum BenchmarkType { |
| 28 kNormal_BenchmarkType = 0, |
| 29 kRTree_BenchmarkType, |
| 30 }; |
| 31 |
| 32 struct 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 |
| 41 typedef void (*BenchmarkFunction) |
| 42 (BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*); |
| 43 |
| 44 // Defined below. |
| 45 static void benchmark_playback( |
| 46 BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*)
; |
| 47 static 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 */ |
| 54 struct 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 kNu
mBenchmarks. |
| 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 static SkTArray<Histogram> fHistograms[kNumBenchmarks]; |
| 81 |
| 82 static SkISize getTileSize(size_t i) { |
| 83 // Two of the base benchmarks don't need a tile size. But to maintain si
mplicity |
| 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 |
| 158 SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = { |
| 159 SkISize::Make(256, 256), |
| 160 SkISize::Make(512, 512), |
| 161 SkISize::Make(1024, 1024), |
| 162 }; |
| 163 |
| 164 static 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 */ |
| 180 static 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_BBox
HierarchyType); |
| 189 break; |
| 190 case kRTree_BenchmarkType: |
| 191 msgPrefix.set("RTree"); |
| 192 renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBo
xHierarchyType); |
| 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(), num
Repeats); |
| 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; whic
h 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 dimension
s. |
| 226 */ |
| 227 static 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 */ |
| 241 static 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, "recor
ding", 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 */ |
| 265 static bool benchmark_loop( |
| 266 int argc, |
| 267 char **argv, |
| 268 const BenchmarkControl& benchControl, |
| 269 SkTArray<Histogram>& histogram) { |
| 270 |
| 271 static const SkString timeFormat("%f"); |
| 272 TimerData timerData(timeFormat, timeFormat); |
| 273 for (int index = 1; index < argc; ++index) { |
| 274 BenchTimer timer; |
| 275 SkString path(argv[index]); |
| 276 SkAutoTUnref<SkPicture> pic(pic_from_path(path.c_str())); |
| 277 if (NULL == pic) { |
| 278 SkDebugf("Couldn't create picture. Ignoring path: %s\n", path.c_str(
)); |
| 279 continue; |
| 280 } |
| 281 benchControl.fFunction(benchControl.fType, benchControl.fTileSize, path,
pic, &timer); |
| 282 timerData.appendTimes(&timer, argc - 1 == index); |
| 283 |
| 284 histogram[index - 1].fPath = path; |
| 285 histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu); |
| 286 } |
| 287 |
| 288 const SkString timerResult = timerData.getResult( |
| 289 /*logPerIter = */ false, |
| 290 /*printMin = */ false, |
| 291 /*repeatDraw = */ 1, |
| 292 /*configName = */ benchControl.fName.c_str(), |
| 293 /*showWallTime = */ false, |
| 294 /*showTruncatedWallTime = */ false, |
| 295 /*showCpuTime = */ true, |
| 296 /*showTruncatedCpuTime = */ false, |
| 297 /*showGpuTime = */ false); |
| 298 |
| 299 const char findStr[] = "= "; |
| 300 int pos = timerResult.find(findStr); |
| 301 if (-1 == pos) { |
| 302 SkDebugf("Unexpected output from TimerData::getResult(...). Unable to pa
rse."); |
| 303 return false; |
| 304 } |
| 305 |
| 306 SkScalar cpuTime = SkDoubleToScalar(atof(timerResult.c_str() + pos + sizeof(
findStr) - 1)); |
| 307 if (cpuTime == 0) { // atof returns 0.0 on error. |
| 308 SkDebugf("Unable to read value from timer result.\n"); |
| 309 return false; |
| 310 } |
| 311 return true; |
| 312 } |
| 313 |
| 314 static int 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 static SkTArray<Histogram> histograms[kNumBenchmarks]; |
| 325 |
| 326 for (size_t i = 0; i < kNumBenchmarks; ++i) { |
| 327 histograms[i] = SkTArray<Histogram>(argc - 1); |
| 328 histograms[i].reset(argc - 1); |
| 329 bool success = benchmark_loop( |
| 330 argc, argv, |
| 331 BenchmarkControl::Make(i), |
| 332 histograms[i]); |
| 333 if (!success) { |
| 334 SkDebugf("benchmark_loop failed at index %d", i); |
| 335 } |
| 336 } |
| 337 |
| 338 // Output gnuplot readable histogram data.. |
| 339 const char* pbTitle = "bbh_shootout_playback.dat"; |
| 340 const char* recTitle = "bbh_shootout_record.dat"; |
| 341 SkFILEWStream playbackOut(pbTitle); |
| 342 SkFILEWStream recordOut(recTitle); |
| 343 recordOut.writeText("# "); |
| 344 playbackOut.writeText("# "); |
| 345 for (size_t i = 0; i < kNumBenchmarks; ++i) { |
| 346 SkString out; |
| 347 out.printf("%s ", BenchmarkControl::getBenchmarkName(i).c_str()); |
| 348 if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_recording) { |
| 349 recordOut.writeText(out.c_str()); |
| 350 } |
| 351 if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_playback) { |
| 352 playbackOut.writeText(out.c_str()); |
| 353 } |
| 354 } |
| 355 recordOut.writeText("\n"); |
| 356 playbackOut.writeText("\n"); |
| 357 |
| 358 for (int i = 0; i < argc - 1; ++i) { |
| 359 SkString pbLine; |
| 360 SkString recLine; |
| 361 // ==== Write record info |
| 362 recLine.printf("%d ", i); |
| 363 recLine.appendf("%f ", histograms[0][i].fCpuTime); // Append normal_rec
ord time |
| 364 recLine.appendf("%f", histograms[1][i].fCpuTime); // Append rtree_recor
d time |
| 365 |
| 366 // ==== Write playback info |
| 367 pbLine.printf("%d ", i); |
| 368 pbLine.appendf("%f ", histograms[2][i].fCpuTime); // Start with normal
playback time. |
| 369 // Append all playback benchmark times. |
| 370 for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) { |
| 371 pbLine.appendf("%f ", histograms[j][i].fCpuTime); |
| 372 } |
| 373 pbLine.remove(pbLine.size() - 1, 1); // Remove trailing space from line
. |
| 374 pbLine.appendf("\n"); |
| 375 recLine.appendf("\n"); |
| 376 playbackOut.writeText(pbLine.c_str()); |
| 377 recordOut.writeText(recLine.c_str()); |
| 378 } |
| 379 SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitl
e); |
| 380 |
| 381 return 0; |
| 382 } |
| 383 |
| 384 int main(int argc, char** argv) { |
| 385 return tool_main(argc, argv); |
| 386 } |
| 387 |
OLD | NEW |