bbh_shootout.cpp revision 5f3f681db58d613f5b96caa0af45ba15f776fe96
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"
17#include "TimerData.h"
18
19static const int kNumNormalRecordings = SkBENCHLOOP(10);
20static const int kNumRTreeRecordings = SkBENCHLOOP(10);
21static const int kNumPlaybacks = SkBENCHLOOP(4);
22static const size_t kNumBaseBenchmarks = 3;
23static const size_t kNumTileSizes = 3;
24static const size_t kNumBbhPlaybackBenchmarks = 3;
25static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks;
26
27enum BenchmarkType {
28    kNormal_BenchmarkType = 0,
29    kRTree_BenchmarkType,
30};
31
32struct Histogram {
33    Histogram() {
34        // Make fCpuTime negative so that we don't mess with stats:
35        fCpuTime = SkIntToScalar(-1);
36    }
37    SkScalar fCpuTime;
38    SkString fPath;
39};
40
41typedef void (*BenchmarkFunction)
42    (BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
43
44// Defined below.
45static void benchmark_playback(
46        BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
47static void benchmark_recording(
48        BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
49
50/**
51 * Acts as a POD containing information needed to run a benchmark.
52 * Provides static methods to poll benchmark info from an index.
53 */
54struct BenchmarkControl {
55    SkISize fTileSize;
56    BenchmarkType fType;
57    BenchmarkFunction fFunction;
58    SkString fName;
59
60    /**
61     * Will construct a BenchmarkControl instance from an index between 0 an kNumBenchmarks.
62     */
63    static BenchmarkControl Make(size_t i) {
64        SkASSERT(kNumBenchmarks > i);
65        BenchmarkControl benchControl;
66        benchControl.fTileSize = getTileSize(i);
67        benchControl.fType = getBenchmarkType(i);
68        benchControl.fFunction = getBenchmarkFunc(i);
69        benchControl.fName = getBenchmarkName(i);
70        return benchControl;
71    }
72
73    enum BaseBenchmarks {
74        kNormalRecord = 0,
75        kRTreeRecord,
76        kNormalPlayback,
77    };
78
79    static SkISize fTileSizes[kNumTileSizes];
80
81    static SkISize getTileSize(size_t i) {
82        // Two of the base benchmarks don't need a tile size. But to maintain simplicity
83        // down the pipeline we have to let a couple of values unused.
84        if (i < kNumBaseBenchmarks) {
85            return SkISize::Make(256, 256);
86        }
87        if (i >= kNumBaseBenchmarks && i < kNumBenchmarks) {
88            return fTileSizes[i - kNumBaseBenchmarks];
89        }
90        SkASSERT(0);
91        return SkISize::Make(0, 0);
92    }
93
94    static BenchmarkType getBenchmarkType(size_t i) {
95        if (i < kNumBaseBenchmarks) {
96            switch (i) {
97                case kNormalRecord:
98                    return kNormal_BenchmarkType;
99                case kNormalPlayback:
100                    return kNormal_BenchmarkType;
101                case kRTreeRecord:
102                    return kRTree_BenchmarkType;
103            }
104        }
105        if (i < kNumBenchmarks) {
106            return kRTree_BenchmarkType;
107        }
108        SkASSERT(0);
109        return kRTree_BenchmarkType;
110    }
111
112    static BenchmarkFunction getBenchmarkFunc(size_t i) {
113        // Base functions.
114        switch (i) {
115            case kNormalRecord:
116                return benchmark_recording;
117            case kNormalPlayback:
118                return benchmark_playback;
119            case kRTreeRecord:
120                return benchmark_recording;
121        }
122        // RTree playbacks
123        if (i < kNumBenchmarks) {
124            return benchmark_playback;
125        }
126        SkASSERT(0);
127        return NULL;
128    }
129
130    static SkString getBenchmarkName(size_t i) {
131        // Base benchmark names
132        switch (i) {
133            case kNormalRecord:
134                return SkString("normal_recording");
135            case kNormalPlayback:
136                return SkString("normal_playback");
137            case kRTreeRecord:
138                return SkString("rtree_recording");
139        }
140        // RTree benchmark names.
141        if (i < kNumBenchmarks) {
142            SkASSERT(i >= kNumBaseBenchmarks);
143            SkString name;
144            name.printf("rtree_playback_%dx%d",
145                    fTileSizes[i - kNumBaseBenchmarks].fWidth,
146                    fTileSizes[i - kNumBaseBenchmarks].fHeight);
147            return name;
148
149        } else {
150            SkASSERT(0);
151        }
152        return SkString("");
153    }
154
155};
156
157SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = {
158    SkISize::Make(256, 256),
159    SkISize::Make(512, 512),
160    SkISize::Make(1024, 1024),
161};
162
163static SkPicture* pic_from_path(const char path[]) {
164    SkFILEStream stream(path);
165    if (!stream.isValid()) {
166        SkDebugf("-- Can't open '%s'\n", path);
167        return NULL;
168    }
169    return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap);
170}
171
172/**
173 * This function is the sink to which all work ends up going.
174 * Renders the picture into the renderer. It may or may not use an RTree.
175 * The renderer is chosen upstream. If we want to measure recording, we will
176 * use a RecordPictureRenderer. If we want to measure rendering, we eill use a
177 * TiledPictureRenderer.
178 */
179static void do_benchmark_work(sk_tools::PictureRenderer* renderer,
180        int benchmarkType, const SkString& path, SkPicture* pic,
181        const int numRepeats, const char *msg, BenchTimer* timer) {
182    SkString msgPrefix;
183
184    switch (benchmarkType){
185        case kNormal_BenchmarkType:
186            msgPrefix.set("Normal");
187            renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
188            break;
189        case kRTree_BenchmarkType:
190            msgPrefix.set("RTree");
191            renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBoxHierarchyType);
192            break;
193        default:
194            SkASSERT(0);
195            break;
196    }
197
198    renderer->init(pic);
199
200    /**
201     * If the renderer is not tiled, assume we are measuring recording.
202     */
203    bool isPlayback = (NULL != renderer->getTiledRenderer());
204
205    SkDebugf("%s %s %s %d times...\n", msgPrefix.c_str(), msg, path.c_str(), numRepeats);
206    for (int i = 0; i < numRepeats; ++i) {
207        renderer->setup();
208        // Render once to fill caches.
209        renderer->render(NULL);
210        // Render again to measure
211        timer->start();
212        bool result = renderer->render(NULL);
213        timer->end();
214        // We only care about a false result on playback. RecordPictureRenderer::render will always
215        // return false because we are passing a NULL file name on purpose; which is fine.
216        if(isPlayback && !result) {
217            SkDebugf("Error rendering during playback.\n");
218        }
219    }
220    renderer->end();
221}
222
223/**
224 * Call do_benchmark_work with a tiled renderer using the default tile dimensions.
225 */
226static void benchmark_playback(
227        BenchmarkType benchmarkType, const SkISize& tileSize,
228        const SkString& path, SkPicture* pic, BenchTimer* timer) {
229    sk_tools::TiledPictureRenderer renderer;
230
231    SkString message("tiled_playback");
232    message.appendf("_%dx%d", tileSize.fWidth, tileSize.fHeight);
233    do_benchmark_work(&renderer, benchmarkType,
234            path, pic, kNumPlaybacks, message.c_str(), timer);
235}
236
237/**
238 * Call do_benchmark_work with a RecordPictureRenderer.
239 */
240static void benchmark_recording(
241        BenchmarkType benchmarkType, const SkISize& tileSize,
242        const SkString& path, SkPicture* pic, BenchTimer* timer) {
243    sk_tools::RecordPictureRenderer renderer;
244    int numRecordings = 0;
245    switch(benchmarkType) {
246        case kRTree_BenchmarkType:
247            numRecordings = kNumRTreeRecordings;
248            break;
249        case kNormal_BenchmarkType:
250            numRecordings = kNumNormalRecordings;
251            break;
252    }
253    do_benchmark_work(&renderer, benchmarkType, path, pic, numRecordings, "recording", timer);
254}
255
256/**
257 * Takes argc,argv along with one of the benchmark functions defined above.
258 * Will loop along all skp files and perform measurments.
259 *
260 * Returns a SkScalar representing CPU time taken during benchmark.
261 * As a side effect, it spits the timer result to stdout.
262 * Will return -1.0 on error.
263 */
264static bool benchmark_loop(
265        int argc,
266        char **argv,
267        const BenchmarkControl& benchControl,
268        Histogram** histogram) {
269
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
283        histogram[index - 1]->fPath = path;
284        histogram[index - 1]->fCpuTime = SkDoubleToScalar(timer.fCpu);
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
313int tool_main(int argc, char** argv);
314int tool_main(int argc, char** argv) {
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
324    Histogram* histograms[kNumBenchmarks];
325
326    for (size_t i = 0; i < kNumBenchmarks; ++i) {
327        histograms[i] = SkNEW_ARRAY(Histogram, argc - 1);
328        bool success = benchmark_loop(
329                argc, argv,
330                BenchmarkControl::Make(i),
331                &histograms[i]);
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
383#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
384int main(int argc, char** argv) {
385    return tool_main(argc, argv);
386}
387#endif
388
389
390