blob: 2adb60160ca53db7fe75f62119a6e2ead882c672 [file] [log] [blame]
Nigel Taod2075ce2018-04-25 15:26:29 +10001// Copyright 2018 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Nigel Tao670b45b2019-02-02 08:10:08 +110015// ----------------
16
Nigel Taod2075ce2018-04-25 15:26:29 +100017// This file contains a hand-written C benchmark of different strategies for
18// decoding PNG data.
19//
20// For a PNG image with width W and height H, the H rows can be decompressed
21// one-at-a-time or all-at-once. Roughly speaking, this corresponds to H versus
22// 1 call into the zlib decoder. The former (call it "fragmented dst") requires
23// less scratch-space memory than the latter ("full dst"): 2 * bytes_per_row
24// instead of H * bytes_per row, but the latter can be faster.
25//
26// The zlib-compressed data can be split into multiple IDAT chunks. Similarly,
27// these chunks can be decompressed separately ("fragmented IDAT") or together
28// ("full IDAT"), again providing a memory vs speed trade-off.
29//
30// This program reports the speed of combining the independent frag/full dst
31// and frag/full IDAT techniques.
32//
Nigel Tao95d02ee2019-01-12 12:34:40 +110033// For example, with gcc 7.3 (and -O3) as of January 2019:
Nigel Taod2075ce2018-04-25 15:26:29 +100034//
35// On ../test/data/hat.png (90 × 112 pixels):
36// name time/op relative
Nigel Taoc6a48b32019-01-19 10:50:40 +110037// FragDstFragIDAT/gcc 289µs ± 1% 1.00x
38// FragDstFullIDAT/gcc 288µs ± 0% 1.00x
39// FullDstFragIDAT/gcc 149µs ± 1% 1.93x
40// FullDstFullIDAT/gcc 148µs ± 1% 1.95x
Nigel Taod2075ce2018-04-25 15:26:29 +100041//
Nigel Tao95d02ee2019-01-12 12:34:40 +110042// On ../test/data/hibiscus.regular.png (312 × 442 pixels):
Nigel Taod2075ce2018-04-25 15:26:29 +100043// name time/op relative
Nigel Taoc6a48b32019-01-19 10:50:40 +110044// FragDstFragIDAT/gcc 2.49ms ± 0% 1.00x
45// FragDstFullIDAT/gcc 2.49ms ± 0% 1.00x
46// FullDstFragIDAT/gcc 2.08ms ± 0% 1.20x
47// FullDstFullIDAT/gcc 2.02ms ± 1% 1.23x
Nigel Taod2075ce2018-04-25 15:26:29 +100048//
49// On ../test/data/harvesters.png (1165 × 859 pixels):
50// name time/op relative
Nigel Taoc6a48b32019-01-19 10:50:40 +110051// FragDstFragIDAT/gcc 15.6ms ± 2% 1.00x
52// FragDstFullIDAT/gcc 15.4ms ± 0% 1.01x
53// FullDstFragIDAT/gcc 14.4ms ± 0% 1.08x
54// FullDstFullIDAT/gcc 14.1ms ± 0% 1.10x
Nigel Taod2075ce2018-04-25 15:26:29 +100055
56#include <errno.h>
57#include <inttypes.h>
58#include <stdio.h>
59#include <string.h>
60#include <sys/time.h>
61#include <unistd.h>
62
Nigel Taoa0bafff2018-07-14 08:59:36 +100063// Wuffs ships as a "single file C library" or "header file library" as per
64// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
65//
66// To use that single file as a "foo.c"-like implementation, instead of a
67// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
68// compiling it.
69#define WUFFS_IMPLEMENTATION
70
Nigel Tao6fb76972021-10-07 14:36:35 +110071// Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when
72// combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs'
73// functions have static storage.
74//
75// This can help the compiler ignore or discard unused code, which can produce
76// faster compiles and smaller binaries. Other motivations are discussed in the
77// "ALLOW STATIC IMPLEMENTATION" section of
78// https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt
79#define WUFFS_CONFIG__STATIC_FUNCTIONS
80
Jimmy Casey3c50ada2018-07-26 17:12:19 +000081// If building this program in an environment that doesn't easily accommodate
Nigel Taod2075ce2018-04-25 15:26:29 +100082// relative includes, you can use the script/inline-c-relative-includes.go
83// program to generate a stand-alone C file.
Nigel Tao95d02ee2019-01-12 12:34:40 +110084#include "../release/c/wuffs-unsupported-snapshot.c"
Nigel Taod2075ce2018-04-25 15:26:29 +100085
86// The order matters here. Clang also defines "__GNUC__".
87#if defined(__clang__)
Nigel Taoc0bd9df2020-03-26 21:55:44 +110088const char* g_cc = "clang";
89const char* g_cc_version = __clang_version__;
Nigel Taod2075ce2018-04-25 15:26:29 +100090#elif defined(__GNUC__)
Nigel Taoc0bd9df2020-03-26 21:55:44 +110091const char* g_cc = "gcc";
92const char* g_cc_version = __VERSION__;
Nigel Taod2075ce2018-04-25 15:26:29 +100093#elif defined(_MSC_VER)
Nigel Taoc0bd9df2020-03-26 21:55:44 +110094const char* g_cc = "cl";
95const char* g_cc_version = "???";
Nigel Taod2075ce2018-04-25 15:26:29 +100096#else
Nigel Taoc0bd9df2020-03-26 21:55:44 +110097const char* g_cc = "cc";
98const char* g_cc_version = "???";
Nigel Taod2075ce2018-04-25 15:26:29 +100099#endif
100
Nigel Tao2914bae2020-02-26 09:40:30 +1100101static inline uint32_t //
102load_u32be(uint8_t* p) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000103 return ((uint32_t)(p[0]) << 24) | ((uint32_t)(p[1]) << 16) |
104 ((uint32_t)(p[2]) << 8) | ((uint32_t)(p[3]) << 0);
105}
106
107// Limit the input PNG image (and therefore its IDAT data) to (64 MiB - 1 byte)
108// compressed, in up to 1024 IDAT chunks, and 256 MiB and 16384 × 16384 pixels
109// uncompressed. This is a limitation of this program (which uses the Wuffs
110// standard library), not a limitation of Wuffs per se.
Nigel Taofdac24a2020-03-06 21:53:08 +1100111#define DST_BUFFER_ARRAY_SIZE (256 * 1024 * 1024)
112#define SRC_BUFFER_ARRAY_SIZE (64 * 1024 * 1024)
Nigel Taod2075ce2018-04-25 15:26:29 +1000113#define MAX_DIMENSION (16384)
114#define MAX_IDAT_CHUNKS (1024)
115
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100116uint8_t g_dst_buffer_array[DST_BUFFER_ARRAY_SIZE] = {0};
117size_t g_dst_len = 0;
118uint8_t g_src_buffer_array[SRC_BUFFER_ARRAY_SIZE] = {0};
119size_t g_src_len = 0;
120uint8_t g_idat_buffer_array[SRC_BUFFER_ARRAY_SIZE] = {0};
Nigel Taod2075ce2018-04-25 15:26:29 +1000121// The n'th IDAT chunk data (where n is a zero-based count) is in
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100122// g_idat_buffer_array[i:j], where i = g_idat_splits[n+0] and j =
123// g_idat_splits[n+1].
124size_t g_idat_splits[MAX_IDAT_CHUNKS + 1] = {0};
125uint32_t g_num_idat_chunks = 0;
Nigel Taod2075ce2018-04-25 15:26:29 +1000126
Nigel Taofdac24a2020-03-06 21:53:08 +1100127#define WORK_BUFFER_ARRAY_SIZE \
128 WUFFS_ZLIB__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE
129#if WORK_BUFFER_ARRAY_SIZE > 0
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100130uint8_t g_work_buffer_array[WORK_BUFFER_ARRAY_SIZE];
Nigel Tao53760ce2019-02-16 14:18:25 +1100131#else
132// Not all C/C++ compilers support 0-length arrays.
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100133uint8_t g_work_buffer_array[1];
Nigel Tao53760ce2019-02-16 14:18:25 +1100134#endif
135
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100136uint32_t g_width = 0;
137uint32_t g_height = 0;
138uint64_t g_bytes_per_pixel = 0;
139uint64_t g_bytes_per_row = 0;
140uint64_t g_bytes_per_frame = 0;
Nigel Taod2075ce2018-04-25 15:26:29 +1000141
Nigel Tao2914bae2020-02-26 09:40:30 +1100142const char* //
143read_stdin() {
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100144 while (g_src_len < SRC_BUFFER_ARRAY_SIZE) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000145 const int stdin_fd = 0;
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100146 ssize_t n = read(stdin_fd, g_src_buffer_array + g_src_len,
147 SRC_BUFFER_ARRAY_SIZE - g_src_len);
Nigel Taod2075ce2018-04-25 15:26:29 +1000148 if (n > 0) {
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100149 g_src_len += n;
Nigel Taod2075ce2018-04-25 15:26:29 +1000150 } else if (n == 0) {
151 return NULL;
152 } else if (errno == EINTR) {
153 // No-op.
154 } else {
155 return strerror(errno);
156 }
157 }
158 return "input is too large";
159}
160
Nigel Tao2914bae2020-02-26 09:40:30 +1100161const char* //
162process_png_chunks(uint8_t* p, size_t n) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000163 while (n > 0) {
164 // Process the 8 byte chunk header.
165 if (n < 8) {
166 return "invalid PNG chunk";
167 }
168 uint32_t chunk_len = load_u32be(p + 0);
169 uint32_t chunk_type = load_u32be(p + 4);
170 p += 8;
171 n -= 8;
172
173 // Process the chunk payload.
174 if (n < chunk_len) {
175 return "short PNG chunk data";
176 }
177 switch (chunk_type) {
178 case 0x49484452: // "IHDR"
179 if (chunk_len != 13) {
180 return "invalid PNG IDAT chunk";
181 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100182 g_width = load_u32be(p + 0);
183 g_height = load_u32be(p + 4);
184 if ((g_width == 0) || (g_height == 0)) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000185 return "image dimensions are too small";
186 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100187 if ((g_width > MAX_DIMENSION) || (g_height > MAX_DIMENSION)) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000188 return "image dimensions are too large";
189 }
190 if (p[8] != 8) {
191 return "unsupported PNG bit depth";
192 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100193 if (g_bytes_per_pixel != 0) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000194 return "duplicate PNG IHDR chunk";
195 }
196 // Process the color type, as per the PNG spec table 11.1.
197 switch (p[9]) {
198 case 0:
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100199 g_bytes_per_pixel = 1;
Nigel Taod2075ce2018-04-25 15:26:29 +1000200 break;
201 case 2:
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100202 g_bytes_per_pixel = 3;
Nigel Taod2075ce2018-04-25 15:26:29 +1000203 break;
204 case 3:
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100205 g_bytes_per_pixel = 1;
Nigel Taod2075ce2018-04-25 15:26:29 +1000206 break;
207 case 4:
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100208 g_bytes_per_pixel = 2;
Nigel Taod2075ce2018-04-25 15:26:29 +1000209 break;
210 case 6:
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100211 g_bytes_per_pixel = 4;
Nigel Taod2075ce2018-04-25 15:26:29 +1000212 break;
213 default:
214 return "unsupported PNG color type";
215 }
216 if (p[12] != 0) {
217 return "unsupported PNG interlacing";
218 }
219 break;
220
221 case 0x49444154: // "IDAT"
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100222 if (g_num_idat_chunks == MAX_IDAT_CHUNKS - 1) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000223 return "too many IDAT chunks";
224 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100225 memcpy(g_idat_buffer_array + g_idat_splits[g_num_idat_chunks], p,
226 chunk_len);
227 g_idat_splits[g_num_idat_chunks + 1] =
228 g_idat_splits[g_num_idat_chunks] + chunk_len;
229 g_num_idat_chunks++;
Nigel Taod2075ce2018-04-25 15:26:29 +1000230 break;
231 }
232 p += chunk_len;
233 n -= chunk_len;
234
235 // Process (and ignore) the 4 byte chunk footer (a checksum).
236 if (n < 4) {
237 return "invalid PNG chunk";
238 }
239 p += 4;
240 n -= 4;
241 }
242 return NULL;
243}
244
Nigel Tao2914bae2020-02-26 09:40:30 +1100245const char* //
246decode_once(bool frag_dst, bool frag_idat) {
Nigel Tao53760ce2019-02-16 14:18:25 +1100247 wuffs_zlib__decoder dec;
Nigel Tao5f8d2da2020-01-10 22:06:50 +1100248 wuffs_base__status status =
Nigel Tao53760ce2019-02-16 14:18:25 +1100249 wuffs_zlib__decoder__initialize(&dec, sizeof dec, WUFFS_VERSION, 0);
Nigel Tao5f8d2da2020-01-10 22:06:50 +1100250 if (!wuffs_base__status__is_ok(&status)) {
251 return wuffs_base__status__message(&status);
Nigel Tao95d02ee2019-01-12 12:34:40 +1100252 }
Nigel Taod2075ce2018-04-25 15:26:29 +1000253
Nigel Tao95d02ee2019-01-12 12:34:40 +1100254 wuffs_base__io_buffer dst = ((wuffs_base__io_buffer){
255 .data = ((wuffs_base__slice_u8){
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100256 .ptr = g_dst_buffer_array,
257 .len = g_bytes_per_frame,
Nigel Tao95d02ee2019-01-12 12:34:40 +1100258 }),
259 });
260 wuffs_base__io_buffer idat = ((wuffs_base__io_buffer){
261 .data = ((wuffs_base__slice_u8){
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100262 .ptr = g_idat_buffer_array,
Nigel Taofdac24a2020-03-06 21:53:08 +1100263 .len = SRC_BUFFER_ARRAY_SIZE,
Nigel Tao95d02ee2019-01-12 12:34:40 +1100264 }),
265 .meta = ((wuffs_base__io_buffer_meta){
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100266 .wi = g_idat_splits[g_num_idat_chunks],
Nigel Tao95d02ee2019-01-12 12:34:40 +1100267 .ri = 0,
268 .pos = 0,
269 .closed = true,
270 }),
271 });
Nigel Taod2075ce2018-04-25 15:26:29 +1000272
273 uint32_t i = 0; // Number of dst fragments processed, if frag_dst.
274 if (frag_dst) {
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100275 dst.data.len = g_bytes_per_row;
Nigel Taod2075ce2018-04-25 15:26:29 +1000276 }
277
278 uint32_t j = 0; // Number of IDAT fragments processed, if frag_idat.
279 if (frag_idat) {
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100280 idat.meta.wi = g_idat_splits[1];
281 idat.meta.closed = (g_num_idat_chunks == 1);
Nigel Taod2075ce2018-04-25 15:26:29 +1000282 }
283
284 while (true) {
Nigel Taofdac24a2020-03-06 21:53:08 +1100285 status =
286 wuffs_zlib__decoder__transform_io(&dec, &dst, &idat,
287 ((wuffs_base__slice_u8){
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100288 .ptr = g_work_buffer_array,
Nigel Taofdac24a2020-03-06 21:53:08 +1100289 .len = WORK_BUFFER_ARRAY_SIZE,
290 }));
Nigel Taod2075ce2018-04-25 15:26:29 +1000291
Nigel Tao5f8d2da2020-01-10 22:06:50 +1100292 if (wuffs_base__status__is_ok(&status)) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000293 break;
294 }
Nigel Tao5f8d2da2020-01-10 22:06:50 +1100295 if ((status.repr == wuffs_base__suspension__short_write) && frag_dst &&
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100296 (i < g_height - 1)) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000297 i++;
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100298 dst.data.len = g_bytes_per_row * (i + 1);
Nigel Taod2075ce2018-04-25 15:26:29 +1000299 continue;
300 }
Nigel Tao5f8d2da2020-01-10 22:06:50 +1100301 if ((status.repr == wuffs_base__suspension__short_read) && frag_idat &&
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100302 (j < g_num_idat_chunks - 1)) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000303 j++;
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100304 idat.meta.wi = g_idat_splits[j + 1];
305 idat.meta.closed = (g_num_idat_chunks == j + 1);
Nigel Taod2075ce2018-04-25 15:26:29 +1000306 continue;
307 }
Nigel Tao5f8d2da2020-01-10 22:06:50 +1100308 return wuffs_base__status__message(&status);
Nigel Taod2075ce2018-04-25 15:26:29 +1000309 }
310
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100311 if (dst.meta.wi != g_bytes_per_frame) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000312 return "unexpected number of bytes decoded";
313 }
314 return NULL;
315}
316
Nigel Tao2914bae2020-02-26 09:40:30 +1100317const char* //
318decode(bool frag_dst, bool frag_idat) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000319 int reps;
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100320 if (g_bytes_per_frame < 100000) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000321 reps = 1000;
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100322 } else if (g_bytes_per_frame < 1000000) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000323 reps = 100;
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100324 } else if (g_bytes_per_frame < 10000000) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000325 reps = 10;
326 } else {
327 reps = 1;
328 }
329
330 struct timeval bench_start_tv;
331 gettimeofday(&bench_start_tv, NULL);
332
Nigel Taod5075c72021-11-02 14:07:58 +1100333 for (int i = 0; i < reps; i++) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000334 const char* msg = decode_once(frag_dst, frag_idat);
335 if (msg) {
336 return msg;
337 }
338 }
339
340 struct timeval bench_finish_tv;
341 gettimeofday(&bench_finish_tv, NULL);
342 int64_t micros =
343 (int64_t)(bench_finish_tv.tv_sec - bench_start_tv.tv_sec) * 1000000 +
344 (int64_t)(bench_finish_tv.tv_usec - bench_start_tv.tv_usec);
345 uint64_t nanos = 1;
346 if (micros > 0) {
347 nanos = (uint64_t)(micros)*1000;
348 }
349
350 printf("Benchmark%sDst%sIDAT/%s\t%8d\t%8" PRIu64 " ns/op\n",
351 frag_dst ? "Frag" : "Full", //
352 frag_idat ? "Frag" : "Full", //
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100353 g_cc, reps, nanos / reps);
Nigel Taod2075ce2018-04-25 15:26:29 +1000354
355 return NULL;
356}
357
Nigel Tao2914bae2020-02-26 09:40:30 +1100358int //
359fail(const char* msg) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000360 const int stderr_fd = 2;
361 write(stderr_fd, msg, strnlen(msg, 4095));
362 write(stderr_fd, "\n", 1);
363 return 1;
364}
365
Nigel Tao2914bae2020-02-26 09:40:30 +1100366int //
367main(int argc, char** argv) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000368 const char* msg = read_stdin();
369 if (msg) {
370 return fail(msg);
371 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100372 if ((g_src_len < 8) || strncmp((const char*)(g_src_buffer_array),
373 "\x89PNG\x0D\x0A\x1A\x0A", 8)) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000374 return fail("invalid PNG");
375 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100376 msg = process_png_chunks(g_src_buffer_array + 8, g_src_len - 8);
Nigel Taod2075ce2018-04-25 15:26:29 +1000377 if (msg) {
378 return fail(msg);
379 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100380 if (g_bytes_per_pixel == 0) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000381 return fail("missing PNG IHDR chunk");
382 }
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100383 if (g_num_idat_chunks == 0) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000384 return fail("missing PNG IDAT chunk");
385 }
386 // The +1 here is for the per-row filter byte.
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100387 g_bytes_per_row = (uint64_t)g_width * g_bytes_per_pixel + 1;
388 g_bytes_per_frame = (uint64_t)g_height * g_bytes_per_row;
389 if (g_bytes_per_frame > DST_BUFFER_ARRAY_SIZE) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000390 return fail("decompressed data is too large");
391 }
392
Nigel Taoc0bd9df2020-03-26 21:55:44 +1100393 printf("# %s version %s\n#\n", g_cc, g_cc_version);
Nigel Taod2075ce2018-04-25 15:26:29 +1000394 printf(
395 "# The output format, including the \"Benchmark\" prefixes, is "
396 "compatible with the\n"
397 "# https://godoc.org/golang.org/x/perf/cmd/benchstat tool. To install "
398 "it, first\n"
Nigel Tao425eefe2021-12-30 22:58:12 +1100399 "# install Go, then run \"go install golang.org/x/perf/cmd/benchstat\".\n");
Nigel Taod2075ce2018-04-25 15:26:29 +1000400
Nigel Taod5075c72021-11-02 14:07:58 +1100401 for (int i = 0; i < 5; i++) {
Nigel Taod2075ce2018-04-25 15:26:29 +1000402 msg = decode(true, true);
403 if (msg) {
404 return fail(msg);
405 }
406 msg = decode(true, false);
407 if (msg) {
408 return fail(msg);
409 }
410 msg = decode(false, true);
411 if (msg) {
412 return fail(msg);
413 }
414 msg = decode(false, false);
415 if (msg) {
416 return fail(msg);
417 }
418 }
419
420 return 0;
421}