nanobench.cpp revision a1193e4b0e34a7e4e1bd33e9708d7341679f8321
1/* 2 * Copyright 2014 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 <ctype.h> 9 10#include "Benchmark.h" 11#include "CrashHandler.h" 12#include "GMBench.h" 13#include "ProcStats.h" 14#include "ResultsWriter.h" 15#include "RecordingBench.h" 16#include "SKPBench.h" 17#include "Stats.h" 18#include "Timer.h" 19 20#include "SkBBoxHierarchy.h" 21#include "SkCanvas.h" 22#include "SkCommonFlags.h" 23#include "SkForceLinking.h" 24#include "SkGraphics.h" 25#include "SkOSFile.h" 26#include "SkPictureRecorder.h" 27#include "SkPictureUtils.h" 28#include "SkString.h" 29#include "SkSurface.h" 30#include "SkTaskGroup.h" 31 32#if SK_SUPPORT_GPU 33 #include "gl/GrGLDefines.h" 34 #include "GrContextFactory.h" 35 SkAutoTDelete<GrContextFactory> gGrFactory; 36#endif 37 38__SK_FORCE_IMAGE_DECODER_LINKING; 39 40static const int kAutoTuneLoops = 0; 41 42static const int kDefaultLoops = 43#ifdef SK_DEBUG 44 1; 45#else 46 kAutoTuneLoops; 47#endif 48 49static SkString loops_help_txt() { 50 SkString help; 51 help.printf("Number of times to run each bench. Set this to %d to auto-" 52 "tune for each bench. Timings are only reported when auto-tuning.", 53 kAutoTuneLoops); 54 return help; 55} 56 57DEFINE_int32(loops, kDefaultLoops, loops_help_txt().c_str()); 58 59DEFINE_int32(samples, 10, "Number of samples to measure for each bench."); 60DEFINE_int32(overheadLoops, 100000, "Loops to estimate timer overhead."); 61DEFINE_double(overheadGoal, 0.0001, 62 "Loop until timer overhead is at most this fraction of our measurments."); 63DEFINE_double(gpuMs, 5, "Target bench time in millseconds for GPU."); 64DEFINE_int32(gpuFrameLag, 5, "Overestimate of maximum number of frames GPU allows to lag."); 65DEFINE_bool(gpuCompressAlphaMasks, false, "Compress masks generated from falling back to " 66 "software path rendering."); 67 68DEFINE_string(outResultsFile, "", "If given, write results here as JSON."); 69DEFINE_int32(maxCalibrationAttempts, 3, 70 "Try up to this many times to guess loops for a bench, or skip the bench."); 71DEFINE_int32(maxLoops, 1000000, "Never run a bench more times than this."); 72DEFINE_string(clip, "0,0,1000,1000", "Clip for SKPs."); 73DEFINE_string(scales, "1.0", "Space-separated scales for SKPs."); 74DEFINE_bool(bbh, true, "Build a BBH for SKPs?"); 75DEFINE_bool(mpd, true, "Use MultiPictureDraw for the SKPs?"); 76DEFINE_int32(flushEvery, 10, "Flush --outResultsFile every Nth run."); 77 78static SkString humanize(double ms) { 79 if (FLAGS_verbose) return SkStringPrintf("%llu", (uint64_t)(ms*1e6)); 80 return HumanizeMs(ms); 81} 82#define HUMANIZE(ms) humanize(ms).c_str() 83 84static double time(int loops, Benchmark* bench, SkCanvas* canvas, SkGLContext* gl) { 85 if (canvas) { 86 canvas->clear(SK_ColorWHITE); 87 } 88 WallTimer timer; 89 timer.start(); 90 if (bench) { 91 bench->draw(loops, canvas); 92 } 93 if (canvas) { 94 canvas->flush(); 95 } 96#if SK_SUPPORT_GPU 97 if (gl) { 98 SK_GL(*gl, Flush()); 99 gl->swapBuffers(); 100 } 101#endif 102 timer.end(); 103 return timer.fWall; 104} 105 106static double estimate_timer_overhead() { 107 double overhead = 0; 108 for (int i = 0; i < FLAGS_overheadLoops; i++) { 109 overhead += time(1, NULL, NULL, NULL); 110 } 111 return overhead / FLAGS_overheadLoops; 112} 113 114static int detect_forever_loops(int loops) { 115 // look for a magic run-forever value 116 if (loops < 0) { 117 loops = SK_MaxS32; 118 } 119 return loops; 120} 121 122static int clamp_loops(int loops) { 123 if (loops < 1) { 124 SkDebugf("ERROR: clamping loops from %d to 1. " 125 "There's probably something wrong with the bench.\n", loops); 126 return 1; 127 } 128 if (loops > FLAGS_maxLoops) { 129 SkDebugf("WARNING: clamping loops from %d to FLAGS_maxLoops, %d.\n", loops, FLAGS_maxLoops); 130 return FLAGS_maxLoops; 131 } 132 return loops; 133} 134 135static bool write_canvas_png(SkCanvas* canvas, const SkString& filename) { 136 if (filename.isEmpty()) { 137 return false; 138 } 139 if (kUnknown_SkColorType == canvas->imageInfo().colorType()) { 140 return false; 141 } 142 SkBitmap bmp; 143 bmp.setInfo(canvas->imageInfo()); 144 if (!canvas->readPixels(&bmp, 0, 0)) { 145 SkDebugf("Can't read canvas pixels.\n"); 146 return false; 147 } 148 SkString dir = SkOSPath::Dirname(filename.c_str()); 149 if (!sk_mkdir(dir.c_str())) { 150 SkDebugf("Can't make dir %s.\n", dir.c_str()); 151 return false; 152 } 153 SkFILEWStream stream(filename.c_str()); 154 if (!stream.isValid()) { 155 SkDebugf("Can't write %s.\n", filename.c_str()); 156 return false; 157 } 158 if (!SkImageEncoder::EncodeStream(&stream, bmp, SkImageEncoder::kPNG_Type, 100)) { 159 SkDebugf("Can't encode a PNG.\n"); 160 return false; 161 } 162 return true; 163} 164 165static int kFailedLoops = -2; 166static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, double* samples) { 167 // First figure out approximately how many loops of bench it takes to make overhead negligible. 168 double bench_plus_overhead = 0.0; 169 int round = 0; 170 if (kAutoTuneLoops == FLAGS_loops) { 171 while (bench_plus_overhead < overhead) { 172 if (round++ == FLAGS_maxCalibrationAttempts) { 173 SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n", 174 bench->getUniqueName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead)); 175 return kFailedLoops; 176 } 177 bench_plus_overhead = time(1, bench, canvas, NULL); 178 } 179 } 180 181 // Later we'll just start and stop the timer once but loop N times. 182 // We'll pick N to make timer overhead negligible: 183 // 184 // overhead 185 // ------------------------- < FLAGS_overheadGoal 186 // overhead + N * Bench Time 187 // 188 // where bench_plus_overhead ≈ overhead + Bench Time. 189 // 190 // Doing some math, we get: 191 // 192 // (overhead / FLAGS_overheadGoal) - overhead 193 // ------------------------------------------ < N 194 // bench_plus_overhead - overhead) 195 // 196 // Luckily, this also works well in practice. :) 197 int loops = FLAGS_loops; 198 if (kAutoTuneLoops == loops) { 199 const double numer = overhead / FLAGS_overheadGoal - overhead; 200 const double denom = bench_plus_overhead - overhead; 201 loops = (int)ceil(numer / denom); 202 loops = clamp_loops(loops); 203 } else { 204 loops = detect_forever_loops(loops); 205 } 206 207 for (int i = 0; i < FLAGS_samples; i++) { 208 samples[i] = time(loops, bench, canvas, NULL) / loops; 209 } 210 return loops; 211} 212 213#if SK_SUPPORT_GPU 214static void setup_gl(SkGLContext* gl) { 215 gl->makeCurrent(); 216 // Make sure we're done with whatever came before. 217 SK_GL(*gl, Finish()); 218} 219 220static int gpu_bench(SkGLContext* gl, 221 Benchmark* bench, 222 SkCanvas* canvas, 223 double* samples) { 224 // First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs. 225 int loops = FLAGS_loops; 226 if (kAutoTuneLoops == loops) { 227 loops = 1; 228 double elapsed = 0; 229 do { 230 if (1<<30 == loops) { 231 // We're about to wrap. Something's wrong with the bench. 232 loops = 0; 233 break; 234 } 235 loops *= 2; 236 // If the GPU lets frames lag at all, we need to make sure we're timing 237 // _this_ round, not still timing last round. We force this by looping 238 // more times than any reasonable GPU will allow frames to lag. 239 for (int i = 0; i < FLAGS_gpuFrameLag; i++) { 240 elapsed = time(loops, bench, canvas, gl); 241 } 242 } while (elapsed < FLAGS_gpuMs); 243 244 // We've overshot at least a little. Scale back linearly. 245 loops = (int)ceil(loops * FLAGS_gpuMs / elapsed); 246 loops = clamp_loops(loops); 247 248 // Might as well make sure we're not still timing our calibration. 249 SK_GL(*gl, Finish()); 250 } else { 251 loops = detect_forever_loops(loops); 252 } 253 254 // Pretty much the same deal as the calibration: do some warmup to make 255 // sure we're timing steady-state pipelined frames. 256 for (int i = 0; i < FLAGS_gpuFrameLag; i++) { 257 time(loops, bench, canvas, gl); 258 } 259 260 // Now, actually do the timing! 261 for (int i = 0; i < FLAGS_samples; i++) { 262 samples[i] = time(loops, bench, canvas, gl) / loops; 263 } 264 return loops; 265} 266#endif 267 268static SkString to_lower(const char* str) { 269 SkString lower(str); 270 for (size_t i = 0; i < lower.size(); i++) { 271 lower[i] = tolower(lower[i]); 272 } 273 return lower; 274} 275 276struct Config { 277 const char* name; 278 Benchmark::Backend backend; 279 SkColorType color; 280 SkAlphaType alpha; 281 int samples; 282#if SK_SUPPORT_GPU 283 GrContextFactory::GLContextType ctxType; 284 bool useDFText; 285#else 286 int bogusInt; 287 bool bogusBool; 288#endif 289}; 290 291struct Target { 292 explicit Target(const Config& c) : config(c) {} 293 const Config config; 294 SkAutoTDelete<SkSurface> surface; 295#if SK_SUPPORT_GPU 296 SkGLContext* gl; 297#endif 298}; 299 300static bool is_cpu_config_allowed(const char* name) { 301 for (int i = 0; i < FLAGS_config.count(); i++) { 302 if (to_lower(FLAGS_config[i]).equals(name)) { 303 return true; 304 } 305 } 306 return false; 307} 308 309#if SK_SUPPORT_GPU 310static bool is_gpu_config_allowed(const char* name, GrContextFactory::GLContextType ctxType, 311 int sampleCnt) { 312 if (!is_cpu_config_allowed(name)) { 313 return false; 314 } 315 if (const GrContext* ctx = gGrFactory->get(ctxType)) { 316 return sampleCnt <= ctx->getMaxSampleCount(); 317 } 318 return false; 319} 320#endif 321 322#if SK_SUPPORT_GPU 323#define kBogusGLContextType GrContextFactory::kNative_GLContextType 324#else 325#define kBogusGLContextType 0 326#endif 327 328// Append all configs that are enabled and supported. 329static void create_configs(SkTDArray<Config>* configs) { 330 #define CPU_CONFIG(name, backend, color, alpha) \ 331 if (is_cpu_config_allowed(#name)) { \ 332 Config config = { #name, Benchmark::backend, color, alpha, 0, \ 333 kBogusGLContextType, false }; \ 334 configs->push(config); \ 335 } 336 337 if (FLAGS_cpu) { 338 CPU_CONFIG(nonrendering, kNonRendering_Backend, kUnknown_SkColorType, kUnpremul_SkAlphaType) 339 CPU_CONFIG(8888, kRaster_Backend, kN32_SkColorType, kPremul_SkAlphaType) 340 CPU_CONFIG(565, kRaster_Backend, kRGB_565_SkColorType, kOpaque_SkAlphaType) 341 } 342 343#if SK_SUPPORT_GPU 344 #define GPU_CONFIG(name, ctxType, samples, useDFText) \ 345 if (is_gpu_config_allowed(#name, GrContextFactory::ctxType, samples)) { \ 346 Config config = { \ 347 #name, \ 348 Benchmark::kGPU_Backend, \ 349 kN32_SkColorType, \ 350 kPremul_SkAlphaType, \ 351 samples, \ 352 GrContextFactory::ctxType, \ 353 useDFText }; \ 354 configs->push(config); \ 355 } 356 357 if (FLAGS_gpu) { 358 GPU_CONFIG(gpu, kNative_GLContextType, 0, false) 359 GPU_CONFIG(msaa4, kNative_GLContextType, 4, false) 360 GPU_CONFIG(msaa16, kNative_GLContextType, 16, false) 361 GPU_CONFIG(nvprmsaa4, kNVPR_GLContextType, 4, false) 362 GPU_CONFIG(nvprmsaa16, kNVPR_GLContextType, 16, false) 363 GPU_CONFIG(gpudft, kNative_GLContextType, 0, true) 364 GPU_CONFIG(debug, kDebug_GLContextType, 0, false) 365 GPU_CONFIG(nullgpu, kNull_GLContextType, 0, false) 366#ifdef SK_ANGLE 367 GPU_CONFIG(angle, kANGLE_GLContextType, 0, false) 368#endif 369 } 370#endif 371} 372 373// If bench is enabled for config, returns a Target* for it, otherwise NULL. 374static Target* is_enabled(Benchmark* bench, const Config& config) { 375 if (!bench->isSuitableFor(config.backend)) { 376 return NULL; 377 } 378 379 SkImageInfo info = SkImageInfo::Make(bench->getSize().fX, bench->getSize().fY, 380 config.color, config.alpha); 381 382 Target* target = new Target(config); 383 384 if (Benchmark::kRaster_Backend == config.backend) { 385 target->surface.reset(SkSurface::NewRaster(info)); 386 } 387#if SK_SUPPORT_GPU 388 else if (Benchmark::kGPU_Backend == config.backend) { 389 uint32_t flags = config.useDFText ? SkSurfaceProps::kUseDistanceFieldFonts_Flag : 0; 390 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType); 391 target->surface.reset(SkSurface::NewRenderTarget(gGrFactory->get(config.ctxType), 392 SkSurface::kNo_Budgeted, info, 393 config.samples, &props)); 394 target->gl = gGrFactory->getGLContext(config.ctxType); 395 } 396#endif 397 398 if (Benchmark::kNonRendering_Backend != config.backend && !target->surface.get()) { 399 delete target; 400 return NULL; 401 } 402 return target; 403} 404 405// Creates targets for a benchmark and a set of configs. 406static void create_targets(SkTDArray<Target*>* targets, Benchmark* b, 407 const SkTDArray<Config>& configs) { 408 for (int i = 0; i < configs.count(); ++i) { 409 if (Target* t = is_enabled(b, configs[i])) { 410 targets->push(t); 411 } 412 413 } 414} 415 416#if SK_SUPPORT_GPU 417static void fill_gpu_options(ResultsWriter* log, SkGLContext* ctx) { 418 const GrGLubyte* version; 419 SK_GL_RET(*ctx, version, GetString(GR_GL_VERSION)); 420 log->configOption("GL_VERSION", (const char*)(version)); 421 422 SK_GL_RET(*ctx, version, GetString(GR_GL_RENDERER)); 423 log->configOption("GL_RENDERER", (const char*) version); 424 425 SK_GL_RET(*ctx, version, GetString(GR_GL_VENDOR)); 426 log->configOption("GL_VENDOR", (const char*) version); 427 428 SK_GL_RET(*ctx, version, GetString(GR_GL_SHADING_LANGUAGE_VERSION)); 429 log->configOption("GL_SHADING_LANGUAGE_VERSION", (const char*) version); 430} 431#endif 432 433class BenchmarkStream { 434public: 435 BenchmarkStream() : fBenches(BenchRegistry::Head()) 436 , fGMs(skiagm::GMRegistry::Head()) 437 , fCurrentRecording(0) 438 , fCurrentScale(0) 439 , fCurrentSKP(0) 440 , fCurrentUseMPD(0) { 441 for (int i = 0; i < FLAGS_skps.count(); i++) { 442 if (SkStrEndsWith(FLAGS_skps[i], ".skp")) { 443 fSKPs.push_back() = FLAGS_skps[i]; 444 } else { 445 SkOSFile::Iter it(FLAGS_skps[i], ".skp"); 446 SkString path; 447 while (it.next(&path)) { 448 fSKPs.push_back() = SkOSPath::Join(FLAGS_skps[0], path.c_str()); 449 } 450 } 451 } 452 453 if (4 != sscanf(FLAGS_clip[0], "%d,%d,%d,%d", 454 &fClip.fLeft, &fClip.fTop, &fClip.fRight, &fClip.fBottom)) { 455 SkDebugf("Can't parse %s from --clip as an SkIRect.\n", FLAGS_clip[0]); 456 exit(1); 457 } 458 459 for (int i = 0; i < FLAGS_scales.count(); i++) { 460 if (1 != sscanf(FLAGS_scales[i], "%f", &fScales.push_back())) { 461 SkDebugf("Can't parse %s from --scales as an SkScalar.\n", FLAGS_scales[i]); 462 exit(1); 463 } 464 } 465 466 fUseMPDs.push_back() = false; 467 if (FLAGS_mpd) { 468 fUseMPDs.push_back() = true; 469 } 470 } 471 472 static bool ReadPicture(const char* path, SkAutoTUnref<SkPicture>* pic) { 473 // Not strictly necessary, as it will be checked again later, 474 // but helps to avoid a lot of pointless work if we're going to skip it. 475 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, path)) { 476 return false; 477 } 478 479 SkAutoTDelete<SkStream> stream(SkStream::NewFromFile(path)); 480 if (stream.get() == NULL) { 481 SkDebugf("Could not read %s.\n", path); 482 return false; 483 } 484 485 pic->reset(SkPicture::CreateFromStream(stream.get())); 486 if (pic->get() == NULL) { 487 SkDebugf("Could not read %s as an SkPicture.\n", path); 488 return false; 489 } 490 return true; 491 } 492 493 Benchmark* next() { 494 if (fBenches) { 495 Benchmark* bench = fBenches->factory()(NULL); 496 fBenches = fBenches->next(); 497 fSourceType = "bench"; 498 fBenchType = "micro"; 499 return bench; 500 } 501 502 while (fGMs) { 503 SkAutoTDelete<skiagm::GM> gm(fGMs->factory()(NULL)); 504 fGMs = fGMs->next(); 505 if (gm->getFlags() & skiagm::GM::kAsBench_Flag) { 506 fSourceType = "gm"; 507 fBenchType = "micro"; 508 return SkNEW_ARGS(GMBench, (gm.detach())); 509 } 510 } 511 512 // First add all .skps as RecordingBenches. 513 while (fCurrentRecording < fSKPs.count()) { 514 const SkString& path = fSKPs[fCurrentRecording++]; 515 SkAutoTUnref<SkPicture> pic; 516 if (!ReadPicture(path.c_str(), &pic)) { 517 continue; 518 } 519 SkString name = SkOSPath::Basename(path.c_str()); 520 fSourceType = "skp"; 521 fBenchType = "recording"; 522 fSKPBytes = static_cast<double>(SkPictureUtils::ApproximateBytesUsed(pic)); 523 fSKPOps = pic->approximateOpCount(); 524 return SkNEW_ARGS(RecordingBench, (name.c_str(), pic.get(), FLAGS_bbh)); 525 } 526 527 // Then once each for each scale as SKPBenches (playback). 528 while (fCurrentScale < fScales.count()) { 529 while (fCurrentSKP < fSKPs.count()) { 530 const SkString& path = fSKPs[fCurrentSKP]; 531 SkAutoTUnref<SkPicture> pic; 532 if (!ReadPicture(path.c_str(), &pic)) { 533 fCurrentSKP++; 534 continue; 535 } 536 537 while (fCurrentUseMPD < fUseMPDs.count()) { 538 if (FLAGS_bbh) { 539 // The SKP we read off disk doesn't have a BBH. Re-record so it grows one. 540 SkRTreeFactory factory; 541 SkPictureRecorder recorder; 542 static const int kFlags = SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag; 543 pic->playback(recorder.beginRecording(pic->cullRect().width(), 544 pic->cullRect().height(), 545 &factory, 546 fUseMPDs[fCurrentUseMPD] ? kFlags : 0)); 547 pic.reset(recorder.endRecording()); 548 } 549 SkString name = SkOSPath::Basename(path.c_str()); 550 fSourceType = "skp"; 551 fBenchType = "playback"; 552 return SkNEW_ARGS(SKPBench, 553 (name.c_str(), pic.get(), fClip, 554 fScales[fCurrentScale], fUseMPDs[fCurrentUseMPD++])); 555 } 556 fCurrentUseMPD = 0; 557 fCurrentSKP++; 558 } 559 fCurrentSKP = 0; 560 fCurrentScale++; 561 } 562 563 return NULL; 564 } 565 566 void fillCurrentOptions(ResultsWriter* log) const { 567 log->configOption("source_type", fSourceType); 568 log->configOption("bench_type", fBenchType); 569 if (0 == strcmp(fSourceType, "skp")) { 570 log->configOption("clip", 571 SkStringPrintf("%d %d %d %d", fClip.fLeft, fClip.fTop, 572 fClip.fRight, fClip.fBottom).c_str()); 573 log->configOption("scale", SkStringPrintf("%.2g", fScales[fCurrentScale]).c_str()); 574 if (fCurrentUseMPD > 0) { 575 SkASSERT(1 == fCurrentUseMPD || 2 == fCurrentUseMPD); 576 log->configOption("multi_picture_draw", fUseMPDs[fCurrentUseMPD-1] ? "true" : "false"); 577 } 578 } 579 if (0 == strcmp(fBenchType, "recording")) { 580 log->metric("bytes", fSKPBytes); 581 log->metric("ops", fSKPOps); 582 } 583 } 584 585private: 586 const BenchRegistry* fBenches; 587 const skiagm::GMRegistry* fGMs; 588 SkIRect fClip; 589 SkTArray<SkScalar> fScales; 590 SkTArray<SkString> fSKPs; 591 SkTArray<bool> fUseMPDs; 592 593 double fSKPBytes, fSKPOps; 594 595 const char* fSourceType; // What we're benching: bench, GM, SKP, ... 596 const char* fBenchType; // How we bench it: micro, recording, playback, ... 597 int fCurrentRecording; 598 int fCurrentScale; 599 int fCurrentSKP; 600 int fCurrentUseMPD; 601}; 602 603int nanobench_main(); 604int nanobench_main() { 605 SetupCrashHandler(); 606 SkAutoGraphics ag; 607 SkTaskGroup::Enabler enabled; 608 609#if SK_SUPPORT_GPU 610 GrContext::Options grContextOpts; 611 grContextOpts.fDrawPathToCompressedTexture = FLAGS_gpuCompressAlphaMasks; 612 gGrFactory.reset(SkNEW_ARGS(GrContextFactory, (grContextOpts))); 613#endif 614 615 if (FLAGS_veryVerbose) { 616 FLAGS_verbose = true; 617 } 618 619 if (kAutoTuneLoops != FLAGS_loops) { 620 FLAGS_samples = 1; 621 FLAGS_gpuFrameLag = 0; 622 } 623 624 if (!FLAGS_writePath.isEmpty()) { 625 SkDebugf("Writing files to %s.\n", FLAGS_writePath[0]); 626 if (!sk_mkdir(FLAGS_writePath[0])) { 627 SkDebugf("Could not create %s. Files won't be written.\n", FLAGS_writePath[0]); 628 FLAGS_writePath.set(0, NULL); 629 } 630 } 631 632 SkAutoTDelete<ResultsWriter> log(SkNEW(ResultsWriter)); 633 if (!FLAGS_outResultsFile.isEmpty()) { 634 log.reset(SkNEW(NanoJSONResultsWriter(FLAGS_outResultsFile[0]))); 635 } 636 637 if (1 == FLAGS_properties.count() % 2) { 638 SkDebugf("ERROR: --properties must be passed with an even number of arguments.\n"); 639 return 1; 640 } 641 for (int i = 1; i < FLAGS_properties.count(); i += 2) { 642 log->property(FLAGS_properties[i-1], FLAGS_properties[i]); 643 } 644 645 if (1 == FLAGS_key.count() % 2) { 646 SkDebugf("ERROR: --key must be passed with an even number of arguments.\n"); 647 return 1; 648 } 649 for (int i = 1; i < FLAGS_key.count(); i += 2) { 650 log->key(FLAGS_key[i-1], FLAGS_key[i]); 651 } 652 653 const double overhead = estimate_timer_overhead(); 654 SkDebugf("Timer overhead: %s\n", HUMANIZE(overhead)); 655 656 SkAutoTMalloc<double> samples(FLAGS_samples); 657 658 if (kAutoTuneLoops != FLAGS_loops) { 659 SkDebugf("Fixed number of loops; times would only be misleading so we won't print them.\n"); 660 } else if (FLAGS_verbose) { 661 // No header. 662 } else if (FLAGS_quiet) { 663 SkDebugf("median\tbench\tconfig\n"); 664 } else { 665 SkDebugf("maxrss\tloops\tmin\tmedian\tmean\tmax\tstddev\t%-*s\tconfig\tbench\n", 666 FLAGS_samples, "samples"); 667 } 668 669 SkTDArray<Config> configs; 670 create_configs(&configs); 671 672 int runs = 0; 673 BenchmarkStream benchStream; 674 while (Benchmark* b = benchStream.next()) { 675 SkAutoTDelete<Benchmark> bench(b); 676 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getUniqueName())) { 677 continue; 678 } 679 680 SkTDArray<Target*> targets; 681 create_targets(&targets, bench.get(), configs); 682 683 if (!targets.isEmpty()) { 684 log->bench(bench->getUniqueName(), bench->getSize().fX, bench->getSize().fY); 685 bench->preDraw(); 686 } 687 for (int j = 0; j < targets.count(); j++) { 688 SkCanvas* canvas = targets[j]->surface.get() ? targets[j]->surface->getCanvas() : NULL; 689 const char* config = targets[j]->config.name; 690 691#if SK_SUPPORT_GPU 692 if (Benchmark::kGPU_Backend == targets[j]->config.backend) { 693 setup_gl(targets[j]->gl); 694 } 695#endif 696 697 bench->perCanvasPreDraw(canvas); 698 699 const int loops = 700#if SK_SUPPORT_GPU 701 Benchmark::kGPU_Backend == targets[j]->config.backend 702 ? gpu_bench(targets[j]->gl, bench.get(), canvas, samples.get()) 703 : 704#endif 705 cpu_bench( overhead, bench.get(), canvas, samples.get()); 706 707 bench->perCanvasPostDraw(canvas); 708 709 if (canvas && !FLAGS_writePath.isEmpty() && FLAGS_writePath[0]) { 710 SkString pngFilename = SkOSPath::Join(FLAGS_writePath[0], config); 711 pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getUniqueName()); 712 pngFilename.append(".png"); 713 write_canvas_png(canvas, pngFilename); 714 } 715 716 if (kFailedLoops == loops) { 717 // Can't be timed. A warning note has already been printed. 718 continue; 719 } 720 721 Stats stats(samples.get(), FLAGS_samples); 722 log->config(config); 723 log->configOption("name", bench->getName()); 724 benchStream.fillCurrentOptions(log.get()); 725#if SK_SUPPORT_GPU 726 if (Benchmark::kGPU_Backend == targets[j]->config.backend) { 727 fill_gpu_options(log.get(), targets[j]->gl); 728 } 729#endif 730 log->metric("min_ms", stats.min); 731 if (runs++ % FLAGS_flushEvery == 0) { 732 log->flush(); 733 } 734 735 if (kAutoTuneLoops != FLAGS_loops) { 736 if (targets.count() == 1) { 737 config = ""; // Only print the config if we run the same bench on more than one. 738 } 739 SkDebugf("%4dM\t%s\t%s\n" 740 , sk_tools::getMaxResidentSetSizeMB() 741 , bench->getUniqueName() 742 , config); 743 } else if (FLAGS_verbose) { 744 for (int i = 0; i < FLAGS_samples; i++) { 745 SkDebugf("%s ", HUMANIZE(samples[i])); 746 } 747 SkDebugf("%s\n", bench->getUniqueName()); 748 } else if (FLAGS_quiet) { 749 if (targets.count() == 1) { 750 config = ""; // Only print the config if we run the same bench on more than one. 751 } 752 SkDebugf("%s\t%s\t%s\n", HUMANIZE(stats.median), bench->getUniqueName(), config); 753 } else { 754 const double stddev_percent = 100 * sqrt(stats.var) / stats.mean; 755 SkDebugf("%4dM\t%d\t%s\t%s\t%s\t%s\t%.0f%%\t%s\t%s\t%s\n" 756 , sk_tools::getMaxResidentSetSizeMB() 757 , loops 758 , HUMANIZE(stats.min) 759 , HUMANIZE(stats.median) 760 , HUMANIZE(stats.mean) 761 , HUMANIZE(stats.max) 762 , stddev_percent 763 , stats.plot.c_str() 764 , config 765 , bench->getUniqueName() 766 ); 767 } 768#if SK_SUPPORT_GPU && GR_CACHE_STATS 769 if (FLAGS_veryVerbose && 770 Benchmark::kGPU_Backend == targets[j]->config.backend) { 771 gGrFactory->get(targets[j]->config.ctxType)->printCacheStats(); 772 } 773#endif 774 } 775 targets.deleteAll(); 776 777#if SK_SUPPORT_GPU 778 if (FLAGS_abandonGpuContext) { 779 gGrFactory->abandonContexts(); 780 } 781 if (FLAGS_resetGpuContext || FLAGS_abandonGpuContext) { 782 gGrFactory->destroyContexts(); 783 } 784#endif 785 } 786 787 log->bench("memory_usage", 0,0); 788 log->config("meta"); 789 log->metric("max_rss_mb", sk_tools::getMaxResidentSetSizeMB()); 790 791 return 0; 792} 793 794#if !defined SK_BUILD_FOR_IOS 795int main(int argc, char** argv) { 796 SkCommandLineFlags::Parse(argc, argv); 797 return nanobench_main(); 798} 799#endif 800