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