aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/bench_record.cpp
blob: 45a143f824fe8d76c06e9f56277dcc89209496d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkCommandLineFlags.h"
#include "SkForceLinking.h"
#include "SkGraphics.h"
#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
#include "SkStream.h"
#include "SkString.h"

#include "LazyDecodeBitmap.h"
#include "Stats.h"
#include "Timer.h"

__SK_FORCE_IMAGE_DECODER_LINKING;

DEFINE_string2(skps, r, "skps", "Directory containing SKPs to read and re-record.");
DEFINE_int32(samples, 10, "Number of times to re-record each SKP.");
DEFINE_int32(tileGridSize, 512, "Set the tile grid size. Has no effect if bbh is not set to tilegrid.");
DEFINE_string(bbh, "", "Turn on the bbh and select the type, one of rtree, tilegrid");
DEFINE_bool(skr, false, "Record SKR instead of SKP.");
DEFINE_string(match, "", "The usual filters on file names of SKPs to bench.");
DEFINE_string(timescale, "us", "Print times in ms, us, or ns");
DEFINE_double(overheadGoal, 0.0001,
              "Try to make timer overhead at most this fraction of our sample measurements.");
DEFINE_int32(verbose, 0, "0: print min sample; "
                         "1: print min, mean, max and noise indication "
                         "2: print all samples");

static double timescale() {
    if (FLAGS_timescale.contains("us")) return 1000;
    if (FLAGS_timescale.contains("ns")) return 1000000;
    return 1;
}

static SkBBHFactory* parse_FLAGS_bbh() {
    if (FLAGS_bbh.isEmpty()) {
        return NULL;
    }

    if (FLAGS_bbh.contains("rtree")) {
        return SkNEW(SkRTreeFactory);
    }
    if (FLAGS_bbh.contains("tilegrid")) {
        SkTileGridFactory::TileGridInfo info;
        info.fTileInterval.set(FLAGS_tileGridSize, FLAGS_tileGridSize);
        info.fMargin.setEmpty();
        info.fOffset.setZero();
        return SkNEW_ARGS(SkTileGridFactory, (info));
    }
    SkDebugf("Invalid bbh type %s, must be one of rtree, tilegrid.\n", FLAGS_bbh[0]);
    return NULL;
}

static void rerecord(const SkPicture& src, SkBBHFactory* bbhFactory) {
    SkPictureRecorder recorder;
    if (FLAGS_skr) {
        src.draw(recorder.EXPERIMENTAL_beginRecording(src.cullRect().width(), 
                                                      src.cullRect().height(), 
                                                      bbhFactory));
    } else {
        src.draw(recorder.  DEPRECATED_beginRecording(src.cullRect().width(), 
                                                      src.cullRect().height(), 
                                                      bbhFactory));
    }
    SkAutoTUnref<SkPicture> pic(recorder.endRecording());
}

static void bench_record(const SkPicture& src,
                         const double timerOverhead,
                         const char* name,
                         SkBBHFactory* bbhFactory) {
    // Rerecord once to warm up any caches.  Otherwise the first sample can be very noisy.
    rerecord(src, bbhFactory);

    // Rerecord once to see how many times we should loop to make timer overhead insignificant.
    WallTimer timer;
    const double scale = timescale();
    do {
        timer.start();
        rerecord(src, bbhFactory);
        timer.end();
    } while (timer.fWall * scale < timerOverhead);  // Loop just in case something bizarre happens.

    // We want (timer overhead / measurement) to be less than FLAGS_overheadGoal.
    // So in each sample, we'll loop enough times to have made that true for our first measurement.
    const int loops = (int)ceil(timerOverhead / timer.fWall / FLAGS_overheadGoal);

    SkAutoTMalloc<double> samples(FLAGS_samples);
    for (int i = 0; i < FLAGS_samples; i++) {
        timer.start();
        for (int j = 0; j < loops; j++) {
            rerecord(src, bbhFactory);
        }
        timer.end();
        samples[i] = timer.fWall * scale / loops;
    }

    Stats stats(samples.get(), FLAGS_samples);
    if (FLAGS_verbose == 0) {
        printf("%g\t%s\n", stats.min, name);
    } else if (FLAGS_verbose == 1) {
        // Get a rough idea of how noisy the measurements were.
        const double noisePercent = 100 * sqrt(stats.var) / stats.mean;
        printf("%g\t%g\t%g\t±%.0f%%\t%s\n", stats.min, stats.mean, stats.max, noisePercent, name);
    } else if (FLAGS_verbose == 2) {
        printf("%s", name);
        for (int i = 0; i < FLAGS_samples; i++) {
            printf("\t%g", samples[i]);
        }
        printf("\n");
    }
}

int tool_main(int argc, char** argv);
int tool_main(int argc, char** argv) {
    SkCommandLineFlags::Parse(argc, argv);
    SkAutoGraphics autoGraphics;

    if (FLAGS_bbh.count() > 1) {
        SkDebugf("Multiple bbh arguments supplied.\n");
        return 1;
    }

    SkAutoTDelete<SkBBHFactory> bbhFactory(parse_FLAGS_bbh());

    // Each run will use this timer overhead estimate to guess how many times it should run.
    static const int kOverheadLoops = 10000000;
    WallTimer timer;
    double overheadEstimate = 0.0;
    const double scale = timescale();
    for (int i = 0; i < kOverheadLoops; i++) {
        timer.start();
        timer.end();
        overheadEstimate += timer.fWall * scale;
    }
    overheadEstimate /= kOverheadLoops;

    SkOSFile::Iter it(FLAGS_skps[0], ".skp");
    SkString filename;
    bool failed = false;
    while (it.next(&filename)) {
        if (SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) {
            continue;
        }

        const SkString path = SkOSPath::Join(FLAGS_skps[0], filename.c_str());

        SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path.c_str()));
        if (!stream) {
            SkDebugf("Could not read %s.\n", path.c_str());
            failed = true;
            continue;
        }
        SkAutoTUnref<SkPicture> src(
            SkPicture::CreateFromStream(stream, sk_tools::LazyDecodeBitmap));
        if (!src) {
            SkDebugf("Could not read %s as an SkPicture.\n", path.c_str());
            failed = true;
            continue;
        }
        bench_record(*src, overheadEstimate, filename.c_str(), bbhFactory.get());
    }
    return failed ? 1 : 0;
}

#if !defined SK_BUILD_FOR_IOS
int main(int argc, char * const argv[]) {
    return tool_main(argc, (char**) argv);
}
#endif