bbh_shootout.cpp revision 47fad70da32b8654c6e37aed27815f16bcc5e2f9
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 "SkTArray.h"
18#include "TimerData.h"
19
20static const int kNumNormalRecordings = SkBENCHLOOP(10);
21static const int kNumRTreeRecordings = SkBENCHLOOP(10);
22static const int kNumPlaybacks = SkBENCHLOOP(4);
23static const size_t kNumBaseBenchmarks = 3;
24static const size_t kNumTileSizes = 3;
25static const size_t kNumBbhPlaybackBenchmarks = 3;
26static const size_t kNumBenchmarks = kNumBaseBenchmarks + kNumBbhPlaybackBenchmarks;
27
28enum BenchmarkType {
29    kNormal_BenchmarkType = 0,
30    kRTree_BenchmarkType,
31};
32
33struct Histogram {
34    Histogram() {
35        // Make fCpuTime negative so that we don't mess with stats:
36        fCpuTime = SkIntToScalar(-1);
37    }
38    SkScalar fCpuTime;
39    SkString fPath;
40};
41
42typedef void (*BenchmarkFunction)
43    (BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
44
45// Defined below.
46static void benchmark_playback(
47        BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
48static void benchmark_recording(
49        BenchmarkType, const SkISize&, const SkString&, SkPicture*, BenchTimer*);
50
51/**
52 * Acts as a POD containing information needed to run a benchmark.
53 * Provides static methods to poll benchmark info from an index.
54 */
55struct BenchmarkControl {
56    SkISize fTileSize;
57    BenchmarkType fType;
58    BenchmarkFunction fFunction;
59    SkString fName;
60
61    /**
62     * Will construct a BenchmarkControl instance from an index between 0 an kNumBenchmarks.
63     */
64    static BenchmarkControl Make(size_t i) {
65        SkASSERT(kNumBenchmarks > i);
66        BenchmarkControl benchControl;
67        benchControl.fTileSize = getTileSize(i);
68        benchControl.fType = getBenchmarkType(i);
69        benchControl.fFunction = getBenchmarkFunc(i);
70        benchControl.fName = getBenchmarkName(i);
71        return benchControl;
72    }
73
74    enum BaseBenchmarks {
75        kNormalRecord = 0,
76        kRTreeRecord,
77        kNormalPlayback,
78    };
79
80    static SkISize fTileSizes[kNumTileSizes];
81
82    static SkISize getTileSize(size_t i) {
83        // Two of the base benchmarks don't need a tile size. But to maintain simplicity
84        // down the pipeline we have to let a couple of values unused.
85        if (i < kNumBaseBenchmarks) {
86            return SkISize::Make(256, 256);
87        }
88        if (i >= kNumBaseBenchmarks && i < kNumBenchmarks) {
89            return fTileSizes[i - kNumBaseBenchmarks];
90        }
91        SkASSERT(0);
92        return SkISize::Make(0, 0);
93    }
94
95    static BenchmarkType getBenchmarkType(size_t i) {
96        if (i < kNumBaseBenchmarks) {
97            switch (i) {
98                case kNormalRecord:
99                    return kNormal_BenchmarkType;
100                case kNormalPlayback:
101                    return kNormal_BenchmarkType;
102                case kRTreeRecord:
103                    return kRTree_BenchmarkType;
104            }
105        }
106        if (i < kNumBenchmarks) {
107            return kRTree_BenchmarkType;
108        }
109        SkASSERT(0);
110        return kRTree_BenchmarkType;
111    }
112
113    static BenchmarkFunction getBenchmarkFunc(size_t i) {
114        // Base functions.
115        switch (i) {
116            case kNormalRecord:
117                return benchmark_recording;
118            case kNormalPlayback:
119                return benchmark_playback;
120            case kRTreeRecord:
121                return benchmark_recording;
122        }
123        // RTree playbacks
124        if (i < kNumBenchmarks) {
125            return benchmark_playback;
126        }
127        SkASSERT(0);
128        return NULL;
129    }
130
131    static SkString getBenchmarkName(size_t i) {
132        // Base benchmark names
133        switch (i) {
134            case kNormalRecord:
135                return SkString("normal_recording");
136            case kNormalPlayback:
137                return SkString("normal_playback");
138            case kRTreeRecord:
139                return SkString("rtree_recording");
140        }
141        // RTree benchmark names.
142        if (i < kNumBenchmarks) {
143            SkASSERT(i >= kNumBaseBenchmarks);
144            SkString name;
145            name.printf("rtree_playback_%dx%d",
146                    fTileSizes[i - kNumBaseBenchmarks].fWidth,
147                    fTileSizes[i - kNumBaseBenchmarks].fHeight);
148            return name;
149
150        } else {
151            SkASSERT(0);
152        }
153        return SkString("");
154    }
155
156};
157
158SkISize BenchmarkControl::fTileSizes[kNumTileSizes] = {
159    SkISize::Make(256, 256),
160    SkISize::Make(512, 512),
161    SkISize::Make(1024, 1024),
162};
163
164static SkPicture* pic_from_path(const char path[]) {
165    SkFILEStream stream(path);
166    if (!stream.isValid()) {
167        SkDebugf("-- Can't open '%s'\n", path);
168        return NULL;
169    }
170    return SkPicture::CreateFromStream(&stream, &sk_tools::LazyDecodeBitmap);
171}
172
173/**
174 * This function is the sink to which all work ends up going.
175 * Renders the picture into the renderer. It may or may not use an RTree.
176 * The renderer is chosen upstream. If we want to measure recording, we will
177 * use a RecordPictureRenderer. If we want to measure rendering, we eill use a
178 * TiledPictureRenderer.
179 */
180static void do_benchmark_work(sk_tools::PictureRenderer* renderer,
181        int benchmarkType, const SkString& path, SkPicture* pic,
182        const int numRepeats, const char *msg, BenchTimer* timer) {
183    SkString msgPrefix;
184
185    switch (benchmarkType){
186        case kNormal_BenchmarkType:
187            msgPrefix.set("Normal");
188            renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kNone_BBoxHierarchyType);
189            break;
190        case kRTree_BenchmarkType:
191            msgPrefix.set("RTree");
192            renderer->setBBoxHierarchyType(sk_tools::PictureRenderer::kRTree_BBoxHierarchyType);
193            break;
194        default:
195            SkASSERT(0);
196            break;
197    }
198
199    renderer->init(pic);
200
201    /**
202     * If the renderer is not tiled, assume we are measuring recording.
203     */
204    bool isPlayback = (NULL != renderer->getTiledRenderer());
205
206    SkDebugf("%s %s %s %d times...\n", msgPrefix.c_str(), msg, path.c_str(), numRepeats);
207    for (int i = 0; i < numRepeats; ++i) {
208        renderer->setup();
209        // Render once to fill caches.
210        renderer->render(NULL);
211        // Render again to measure
212        timer->start();
213        bool result = renderer->render(NULL);
214        timer->end();
215        // We only care about a false result on playback. RecordPictureRenderer::render will always
216        // return false because we are passing a NULL file name on purpose; which is fine.
217        if(isPlayback && !result) {
218            SkDebugf("Error rendering during playback.\n");
219        }
220    }
221    renderer->end();
222}
223
224/**
225 * Call do_benchmark_work with a tiled renderer using the default tile dimensions.
226 */
227static void benchmark_playback(
228        BenchmarkType benchmarkType, const SkISize& tileSize,
229        const SkString& path, SkPicture* pic, BenchTimer* timer) {
230    sk_tools::TiledPictureRenderer renderer;
231
232    SkString message("tiled_playback");
233    message.appendf("_%dx%d", tileSize.fWidth, tileSize.fHeight);
234    do_benchmark_work(&renderer, benchmarkType,
235            path, pic, kNumPlaybacks, message.c_str(), timer);
236}
237
238/**
239 * Call do_benchmark_work with a RecordPictureRenderer.
240 */
241static void benchmark_recording(
242        BenchmarkType benchmarkType, const SkISize& tileSize,
243        const SkString& path, SkPicture* pic, BenchTimer* timer) {
244    sk_tools::RecordPictureRenderer renderer;
245    int numRecordings = 0;
246    switch(benchmarkType) {
247        case kRTree_BenchmarkType:
248            numRecordings = kNumRTreeRecordings;
249            break;
250        case kNormal_BenchmarkType:
251            numRecordings = kNumNormalRecordings;
252            break;
253    }
254    do_benchmark_work(&renderer, benchmarkType, path, pic, numRecordings, "recording", timer);
255}
256
257/**
258 * Takes argc,argv along with one of the benchmark functions defined above.
259 * Will loop along all skp files and perform measurments.
260 *
261 * Returns a SkScalar representing CPU time taken during benchmark.
262 * As a side effect, it spits the timer result to stdout.
263 * Will return -1.0 on error.
264 */
265static bool benchmark_loop(
266        int argc,
267        char **argv,
268        const BenchmarkControl& benchControl,
269        SkTArray<Histogram>& histogram) {
270    static const SkString timeFormat("%f");
271    TimerData timerData(argc - 1);
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);
282
283        histogram[index - 1].fPath = path;
284        histogram[index - 1].fCpuTime = SkDoubleToScalar(timer.fCpu);
285    }
286
287    const SkString timerResult = timerData.getResult(
288            /*doubleFormat = */ timeFormat.c_str(),
289            /*result = */ TimerData::kAvg_Result,
290            /*configName = */ benchControl.fName.c_str(),
291            /*timerFlags = */ TimerData::kCpu_Flag);
292
293    const char findStr[] = "= ";
294    int pos = timerResult.find(findStr);
295    if (-1 == pos) {
296        SkDebugf("Unexpected output from TimerData::getResult(...). Unable to parse.");
297        return false;
298    }
299
300    SkScalar cpuTime = SkDoubleToScalar(atof(timerResult.c_str() + pos + sizeof(findStr) - 1));
301    if (cpuTime == 0) {  // atof returns 0.0 on error.
302        SkDebugf("Unable to read value from timer result.\n");
303        return false;
304    }
305    return true;
306}
307
308int tool_main(int argc, char** argv);
309int tool_main(int argc, char** argv) {
310    SkAutoGraphics ag;
311    SkString usage;
312    usage.printf("Usage: filename [filename]*\n");
313
314    if (argc < 2) {
315        SkDebugf("%s\n", usage.c_str());
316        return -1;
317    }
318
319    SkTArray<Histogram> histograms[kNumBenchmarks];
320
321    for (size_t i = 0; i < kNumBenchmarks; ++i) {
322        histograms[i].reset(argc - 1);
323        bool success = benchmark_loop(
324                argc, argv,
325                BenchmarkControl::Make(i),
326                histograms[i]);
327        if (!success) {
328            SkDebugf("benchmark_loop failed at index %d", i);
329        }
330    }
331
332    // Output gnuplot readable histogram data..
333    const char* pbTitle = "bbh_shootout_playback.dat";
334    const char* recTitle = "bbh_shootout_record.dat";
335    SkFILEWStream playbackOut(pbTitle);
336    SkFILEWStream recordOut(recTitle);
337    recordOut.writeText("# ");
338    playbackOut.writeText("# ");
339    for (size_t i = 0; i < kNumBenchmarks; ++i) {
340        SkString out;
341        out.printf("%s ", BenchmarkControl::getBenchmarkName(i).c_str());
342        if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_recording) {
343            recordOut.writeText(out.c_str());
344        }
345        if (BenchmarkControl::getBenchmarkFunc(i) == &benchmark_playback) {
346            playbackOut.writeText(out.c_str());
347        }
348    }
349    recordOut.writeText("\n");
350    playbackOut.writeText("\n");
351
352    for (int i = 0; i < argc - 1; ++i) {
353        SkString pbLine;
354        SkString recLine;
355        // ==== Write record info
356        recLine.printf("%d ", i);
357        recLine.appendf("%f ", histograms[0][i].fCpuTime);  // Append normal_record time
358        recLine.appendf("%f", histograms[1][i].fCpuTime);  // Append rtree_record time
359
360        // ==== Write playback info
361        pbLine.printf("%d ", i);
362        pbLine.appendf("%f ", histograms[2][i].fCpuTime);  // Start with normal playback time.
363        // Append all playback benchmark times.
364        for (size_t j = kNumBbhPlaybackBenchmarks; j < kNumBenchmarks; ++j) {
365            pbLine.appendf("%f ", histograms[j][i].fCpuTime);
366        }
367        pbLine.remove(pbLine.size() - 1, 1);  // Remove trailing space from line.
368        pbLine.appendf("\n");
369        recLine.appendf("\n");
370        playbackOut.writeText(pbLine.c_str());
371        recordOut.writeText(recLine.c_str());
372    }
373    SkDebugf("\nWrote data to gnuplot-readable files: %s %s\n", pbTitle, recTitle);
374
375    return 0;
376}
377
378#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
379int main(int argc, char** argv) {
380    return tool_main(argc, argv);
381}
382#endif
383