commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 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" |
commit-bot@chromium.org | 49a07ad | 2013-07-22 19:28:40 +0000 | [diff] [blame] | 17 | #include "SkTArray.h" |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 18 | #include "TimerData.h" |
| 19 | |
| 20 | static const int kNumNormalRecordings = SkBENCHLOOP(10); |
| 21 | static const int kNumRTreeRecordings = SkBENCHLOOP(10); |
| 22 | static const int kNumPlaybacks = SkBENCHLOOP(4); |
| 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 | typedef void (*BenchmarkFunction) |
| 43 | (BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*); |
| 44 | |
| 45 | // Defined below. |
| 46 | static void benchmark_playback( |
| 47 | BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*); |
| 48 | static 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 | */ |
| 55 | struct 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 | |
| 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_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 | */ |
| 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, "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 | */ |
| 265 | static bool benchmark_loop( |
| 266 | int argc, |
| 267 | char **argv, |
| 268 | const BenchmarkControl& benchControl, |
commit-bot@chromium.org | 49a07ad | 2013-07-22 19:28:40 +0000 | [diff] [blame] | 269 | SkTArray<Histogram>& histogram) { |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 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 | |
commit-bot@chromium.org | 49a07ad | 2013-07-22 19:28:40 +0000 | [diff] [blame] | 283 | histogram[index - 1].fPath = path; |
| 284 | histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu); |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 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 | |
sglez@google.com | 1d38ae9 | 2013-07-19 20:03:57 +0000 | [diff] [blame] | 313 | int tool_main(int argc, char** argv); |
| 314 | int tool_main(int argc, char** argv) { |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 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 | |
commit-bot@chromium.org | 49a07ad | 2013-07-22 19:28:40 +0000 | [diff] [blame] | 324 | SkTArray<Histogram> histograms[kNumBenchmarks]; |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 325 | |
| 326 | for (size_t i = 0; i < kNumBenchmarks; ++i) { |
commit-bot@chromium.org | 49a07ad | 2013-07-22 19:28:40 +0000 | [diff] [blame] | 327 | histograms[i].reset(argc - 1); |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 328 | bool success = benchmark_loop( |
| 329 | argc, argv, |
| 330 | BenchmarkControl::Make(i), |
commit-bot@chromium.org | 49a07ad | 2013-07-22 19:28:40 +0000 | [diff] [blame] | 331 | histograms[i]); |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 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 | |
sglez@google.com | 5f3f681 | 2013-07-19 20:21:05 +0000 | [diff] [blame] | 383 | #if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL) |
commit-bot@chromium.org | 6645cde | 2013-07-19 18:54:04 +0000 | [diff] [blame] | 384 | int main(int argc, char** argv) { |
| 385 | return tool_main(argc, argv); |
| 386 | } |
sglez@google.com | 5f3f681 | 2013-07-19 20:21:05 +0000 | [diff] [blame] | 387 | #endif |