aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar bsalomon <bsalomon@google.com>2014-08-07 14:28:50 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2014-08-07 14:28:50 -0700
commit6eb03cc06d0bc60da5277a83aa0251a475794b04 (patch)
treef359945cb7d54f915bb91f28f44949fffd42e864
parent72ebb9f1dc1ed4d64127b02fed93446b278069d1 (diff)
Add option to dump images from nanobench.
Add option to set the repeat count to any number, replacs the --runOnce flag. R=mtklein@google.com Author: bsalomon@google.com Review URL: https://codereview.chromium.org/450743002
-rw-r--r--bench/nanobench.cpp109
-rw-r--r--include/core/SkOSFile.h13
-rw-r--r--src/utils/SkOSFile.cpp17
-rw-r--r--tests/OSPathTest.cpp28
4 files changed, 142 insertions, 25 deletions
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index c808183b19..245aa0ed1d 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -31,12 +31,27 @@
__SK_FORCE_IMAGE_DECODER_LINKING;
-#if SK_DEBUG
- DEFINE_bool(runOnce, true, "Run each benchmark just once?");
+static const int kAutoTuneLoops = -1;
+
+static const int kDefaultLoops =
+#ifdef SK_DEBUG
+ 1;
#else
- DEFINE_bool(runOnce, false, "Run each benchmark just once?");
+ kAutoTuneLoops;
#endif
+static SkString loops_help_txt() {
+ SkString help;
+ help.printf("Number of times to run each bench. Set this to %d to auto-"
+ "tune for each bench. Timings are only reported when auto-tuning.",
+ kAutoTuneLoops);
+ return help;
+}
+
+DEFINE_int32(loops, kDefaultLoops, loops_help_txt().c_str());
+
+DEFINE_string2(writePath, w, "", "If set, write benches here as .pngs.");
+
DEFINE_int32(samples, 10, "Number of samples to measure for each bench.");
DEFINE_int32(overheadLoops, 100000, "Loops to estimate timer overhead.");
DEFINE_double(overheadGoal, 0.0001,
@@ -67,6 +82,9 @@ static SkString humanize(double ms) {
#define HUMANIZE(ms) humanize(ms).c_str()
static double time(int loops, Benchmark* bench, SkCanvas* canvas, SkGLContextHelper* gl) {
+ if (canvas) {
+ canvas->clear(SK_ColorWHITE);
+ }
WallTimer timer;
timer.start();
if (bench) {
@@ -105,17 +123,50 @@ static int clamp_loops(int loops) {
return loops;
}
+static bool write_canvas_png(SkCanvas* canvas, const SkString& filename) {
+ if (filename.isEmpty()) {
+ return false;
+ }
+ if (kUnknown_SkColorType == canvas->imageInfo().fColorType) {
+ return false;
+ }
+ SkBitmap bmp;
+ bmp.setInfo(canvas->imageInfo());
+ if (!canvas->readPixels(&bmp, 0, 0)) {
+ SkDebugf("Can't read canvas pixels.\n");
+ return false;
+ }
+ SkString dir = SkOSPath::Dirname(filename.c_str());
+ if (!sk_mkdir(dir.c_str())) {
+ SkDebugf("Can't make dir %s.\n", dir.c_str());
+ return false;
+ }
+ SkFILEWStream stream(filename.c_str());
+ if (!stream.isValid()) {
+ SkDebugf("Can't write %s.\n", filename.c_str());
+ return false;
+ }
+ if (!SkImageEncoder::EncodeStream(&stream, bmp, SkImageEncoder::kPNG_Type, 100)) {
+ SkDebugf("Can't encode a PNG.\n");
+ return false;
+ }
+ return true;
+}
+
+static int kFailedLoops = -2;
static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas, double* samples) {
// First figure out approximately how many loops of bench it takes to make overhead negligible.
double bench_plus_overhead = 0.0;
int round = 0;
- while (bench_plus_overhead < overhead) {
- if (round++ == FLAGS_maxCalibrationAttempts) {
- SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n",
- bench->getName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead));
- return 0;
+ if (kAutoTuneLoops == FLAGS_loops) {
+ while (bench_plus_overhead < overhead) {
+ if (round++ == FLAGS_maxCalibrationAttempts) {
+ SkDebugf("WARNING: Can't estimate loops for %s (%s vs. %s); skipping.\n",
+ bench->getName(), HUMANIZE(bench_plus_overhead), HUMANIZE(overhead));
+ return kFailedLoops;
+ }
+ bench_plus_overhead = time(1, bench, canvas, NULL);
}
- bench_plus_overhead = time(1, bench, canvas, NULL);
}
// Later we'll just start and stop the timer once but loop N times.
@@ -134,9 +185,13 @@ static int cpu_bench(const double overhead, Benchmark* bench, SkCanvas* canvas,
// bench_plus_overhead - overhead)
//
// Luckily, this also works well in practice. :)
- const double numer = overhead / FLAGS_overheadGoal - overhead;
- const double denom = bench_plus_overhead - overhead;
- const int loops = clamp_loops(FLAGS_runOnce ? 1 : (int)ceil(numer / denom));
+ int loops = FLAGS_loops;
+ if (kAutoTuneLoops == loops) {
+ const double numer = overhead / FLAGS_overheadGoal - overhead;
+ const double denom = bench_plus_overhead - overhead;
+ loops = (int)ceil(numer / denom);
+ }
+ loops = clamp_loops(loops);
for (int i = 0; i < FLAGS_samples; i++) {
samples[i] = time(loops, bench, canvas, NULL) / loops;
@@ -154,8 +209,9 @@ static int gpu_bench(SkGLContextHelper* gl,
SK_GL(*gl, Finish());
// First, figure out how many loops it'll take to get a frame up to FLAGS_gpuMs.
- int loops = 1;
- if (!FLAGS_runOnce) {
+ int loops = FLAGS_loops;
+ if (kAutoTuneLoops == loops) {
+ loops = 1;
double elapsed = 0;
do {
loops *= 2;
@@ -474,11 +530,19 @@ int nanobench_main() {
SetupCrashHandler();
SkAutoGraphics ag;
- if (FLAGS_runOnce) {
+ if (kAutoTuneLoops != FLAGS_loops) {
FLAGS_samples = 1;
FLAGS_gpuFrameLag = 0;
}
+ if (!FLAGS_writePath.isEmpty()) {
+ SkDebugf("Writing files to %s.\n", FLAGS_writePath[0]);
+ if (!sk_mkdir(FLAGS_writePath[0])) {
+ SkDebugf("Could not create %s. Files won't be written.\n", FLAGS_writePath[0]);
+ FLAGS_writePath.set(0, NULL);
+ }
+ }
+
MultiResultsWriter log;
SkAutoTDelete<NanoJSONResultsWriter> json;
if (!FLAGS_outResultsFile.isEmpty()) {
@@ -502,8 +566,8 @@ int nanobench_main() {
SkAutoTMalloc<double> samples(FLAGS_samples);
- if (FLAGS_runOnce) {
- SkDebugf("--runOnce is true; times would only be misleading so we won't print them.\n");
+ if (kAutoTuneLoops != FLAGS_loops) {
+ SkDebugf("Fixed number of loops; times would only be misleading so we won't print them.\n");
} else if (FLAGS_verbose) {
// No header.
} else if (FLAGS_quiet) {
@@ -549,7 +613,14 @@ int nanobench_main() {
#endif
cpu_bench( overhead, bench.get(), canvas, samples.get());
- if (loops == 0) {
+ if (canvas && !FLAGS_writePath.isEmpty() && NULL != FLAGS_writePath[0]) {
+ SkString pngFilename = SkOSPath::Join(FLAGS_writePath[0], config);
+ pngFilename = SkOSPath::Join(pngFilename.c_str(), bench->getName());
+ pngFilename.append(".png");
+ write_canvas_png(canvas, pngFilename);
+ }
+
+ if (kFailedLoops == loops) {
// Can't be timed. A warning note has already been printed.
continue;
}
@@ -568,7 +639,7 @@ int nanobench_main() {
log.timer("max_ms", stats.max);
log.timer("stddev_ms", sqrt(stats.var));
- if (FLAGS_runOnce) {
+ if (kAutoTuneLoops != FLAGS_loops) {
if (targets.count() == 1) {
config = ""; // Only print the config if we run the same bench on more than one.
}
diff --git a/include/core/SkOSFile.h b/include/core/SkOSFile.h
index 39eeb4b0b0..69a74dfcaa 100644
--- a/include/core/SkOSFile.h
+++ b/include/core/SkOSFile.h
@@ -123,7 +123,7 @@ public:
/**
* Functions for modifying SkStrings which represent paths on the filesystem.
*/
-class SkOSPath {
+class SkOSPath {
public:
/**
* Assembles rootPath and relativePath into a single path, like this:
@@ -144,6 +144,17 @@ public:
* final slash, or the full name if there is no slash.
*/
static SkString Basename(const char* fullPath);
+
+ /**
+ * Given a qualified file name returns the directory.
+ * Behaves like python's os.path.dirname. If the fullPath is
+ * /dir/subdir/ the return will be /dir/subdir/
+ * @param fullPath Full path to the file.
+ * @return SkString The dir containing the file - anything preceding the
+ * final slash, or the full name if ending in a slash.
+ */
+ static SkString Dirname(const char* fullPath);
+
};
#endif
diff --git a/src/utils/SkOSFile.cpp b/src/utils/SkOSFile.cpp
index 8baf08ffdd..04a4fe910e 100644
--- a/src/utils/SkOSFile.cpp
+++ b/src/utils/SkOSFile.cpp
@@ -8,7 +8,7 @@
SkString SkOSPath::Join(const char *rootPath, const char *relativePath) {
SkString result(rootPath);
- if (!result.endsWith(SkPATH_SEPARATOR)) {
+ if (!result.endsWith(SkPATH_SEPARATOR) && !result.isEmpty()) {
result.appendUnichar(SkPATH_SEPARATOR);
}
result.append(relativePath);
@@ -28,6 +28,21 @@ SkString SkOSPath::Basename(const char* fullPath) {
return SkString(filename);
}
+SkString SkOSPath::Dirname(const char* fullPath) {
+ if (!fullPath) {
+ return SkString();
+ }
+ const char* end = strrchr(fullPath, SkPATH_SEPARATOR);
+ if (NULL == end) {
+ return SkString();
+ }
+ if (end == fullPath) {
+ SkASSERT(fullPath[0] == SkPATH_SEPARATOR);
+ ++end;
+ }
+ return SkString(fullPath, end - fullPath);
+}
+
#ifdef SK_BUILD_FOR_WIN
static uint16_t* concat_to_16(const char src[], const char suffix[])
diff --git a/tests/OSPathTest.cpp b/tests/OSPathTest.cpp
index 1452c3852e..facc6ad3f7 100644
--- a/tests/OSPathTest.cpp
+++ b/tests/OSPathTest.cpp
@@ -10,7 +10,7 @@
#include "Test.h"
/**
- * Test SkOSPath::Join and SkOSPath::Basename.
+ * Test SkOSPath::Join, SkOSPath::Basename, and SkOSPath::Dirname.
* Will use SkOSPath::Join to append filename to dir, test that it works correctly,
* and tests using SkOSPath::Basename on the result.
* @param reporter Reporter for test conditions.
@@ -32,16 +32,28 @@ static void test_dir_with_file(skiatest::Reporter* reporter, SkString dir,
// fullName should be the combined size of dir and file, plus one if
// dir did not include the final path separator.
size_t expectedSize = dir.size() + filename.size();
- if (!dir.endsWith(SkPATH_SEPARATOR)) {
+ if (!dir.endsWith(SkPATH_SEPARATOR) && !dir.isEmpty()) {
expectedSize++;
}
REPORTER_ASSERT(reporter, fullName.size() == expectedSize);
SkString basename = SkOSPath::Basename(fullName.c_str());
+ SkString dirname = SkOSPath::Dirname(fullName.c_str());
// basename should be the same as filename
REPORTER_ASSERT(reporter, basename.equals(filename));
+ // dirname should be the same as dir with any trailing seperators removed.
+ // Except when the the string is just "/".
+ SkString strippedDir = dir;
+ while (strippedDir.size() > 2 && strippedDir[strippedDir.size() - 1] == SkPATH_SEPARATOR) {
+ strippedDir.remove(strippedDir.size() - 1, 1);
+ }
+ if (!dirname.equals(strippedDir)) {
+ SkDebugf("OOUCH %s %s %s\n", dir.c_str(), strippedDir.c_str(), dirname.c_str());
+ }
+ REPORTER_ASSERT(reporter, dirname.equals(strippedDir));
+
// basename will not contain a path separator
REPORTER_ASSERT(reporter, !basename.contains(SkPATH_SEPARATOR));
@@ -78,8 +90,16 @@ DEF_TEST(OSPath, reporter) {
SkString empty = SkOSPath::Basename(NULL);
REPORTER_ASSERT(reporter, empty.size() == 0);
+ // File in root dir
+ dir.printf("%c", SkPATH_SEPARATOR);
+ filename.set("file");
+ test_dir_with_file(reporter, dir, filename);
+
+ // Just the root dir
+ filename.reset();
+ test_dir_with_file(reporter, dir, filename);
+
// Test that NULL can be used for the directory and filename.
SkString emptyPath = SkOSPath::Join(NULL, NULL);
- REPORTER_ASSERT(reporter, emptyPath.size() == 1);
- REPORTER_ASSERT(reporter, emptyPath.contains(SkPATH_SEPARATOR));
+ REPORTER_ASSERT(reporter, emptyPath.isEmpty());
}