1 /*
2 * Copyright 2018 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the Chromium source repository LICENSE file.
5 *
6 * A benchmark test harness for measuring decoding performance of gzip or zlib
7 * (deflate) encoded compressed data. Given a file containing any data, encode
8 * (compress) it into gzip or zlib format and then decode (uncompress). Output
9 * the median and maximum encoding and decoding rates in MB/s.
10 *
11 * Raw deflate (no gzip or zlib stream wrapper) mode is also supported. Select
12 * it with the [raw] argument. Use the [gzip] [zlib] arguments to select those
13 * stream wrappers.
14 *
15 * Note this code can be compiled outside of the Chromium build system against
16 * the system zlib (-lz) with g++ or clang++ as follows:
17 *
18 * g++|clang++ -O3 -Wall -std=c++11 -lstdc++ -lz zlib_bench.cc
19 */
20
21 #include <algorithm>
22 #include <chrono>
23 #include <fstream>
24 #include <memory>
25 #include <string>
26 #include <vector>
27
28 #include <memory.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include "zlib.h"
34
error_exit(const char * error,int code)35 void error_exit(const char* error, int code) {
36 fprintf(stderr, "%s (%d)\n", error, code);
37 exit(code);
38 }
39
string_data(std::string * s)40 inline char* string_data(std::string* s) {
41 return s->empty() ? 0 : &*s->begin();
42 }
43
44 struct Data {
DataData45 Data(size_t s) { data.reset(new (std::nothrow) char[size = s]); }
46 std::unique_ptr<char[]> data;
47 size_t size;
48 };
49
read_file_data_or_exit(const char * name)50 Data read_file_data_or_exit(const char* name) {
51 std::ifstream file(name, std::ios::in | std::ios::binary);
52 if (!file) {
53 perror(name);
54 exit(1);
55 }
56
57 file.seekg(0, std::ios::end);
58 Data data(file.tellg());
59 file.seekg(0, std::ios::beg);
60
61 if (file && data.data)
62 file.read(data.data.get(), data.size);
63
64 if (!file || !data.data || !data.size) {
65 perror((std::string("failed: reading ") + name).c_str());
66 exit(1);
67 }
68
69 return data;
70 }
71
zlib_estimate_compressed_size(size_t input_size)72 size_t zlib_estimate_compressed_size(size_t input_size) {
73 return compressBound(input_size);
74 }
75
76 enum zlib_wrapper {
77 kWrapperNONE,
78 kWrapperZLIB,
79 kWrapperGZIP,
80 kWrapperZRAW,
81 };
82
zlib_stream_wrapper_type(zlib_wrapper type)83 inline int zlib_stream_wrapper_type(zlib_wrapper type) {
84 if (type == kWrapperZLIB) // zlib DEFLATE stream wrapper
85 return MAX_WBITS;
86 if (type == kWrapperGZIP) // gzip DEFLATE stream wrapper
87 return MAX_WBITS + 16;
88 if (type == kWrapperZRAW) // no wrapper, use raw DEFLATE
89 return -MAX_WBITS;
90 error_exit("bad wrapper type", int(type));
91 return 0;
92 }
93
zlib_wrapper_name(zlib_wrapper type)94 const char* zlib_wrapper_name(zlib_wrapper type) {
95 if (type == kWrapperZLIB)
96 return "ZLIB";
97 if (type == kWrapperGZIP)
98 return "GZIP";
99 if (type == kWrapperZRAW)
100 return "RAW";
101 error_exit("bad wrapper type", int(type));
102 return 0;
103 }
104
105 static int zlib_compression_level;
106
zlib_compress(const zlib_wrapper type,const char * input,const size_t input_size,std::string * output,bool resize_output=false)107 void zlib_compress(
108 const zlib_wrapper type,
109 const char* input,
110 const size_t input_size,
111 std::string* output,
112 bool resize_output = false)
113 {
114 if (resize_output)
115 output->resize(zlib_estimate_compressed_size(input_size));
116 size_t output_size = output->size();
117
118 z_stream stream;
119 memset(&stream, 0, sizeof(stream));
120
121 int result = deflateInit2(&stream, zlib_compression_level, Z_DEFLATED,
122 zlib_stream_wrapper_type(type), MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
123 if (result != Z_OK)
124 error_exit("deflateInit2 failed", result);
125
126 stream.next_out = (Bytef*)string_data(output);
127 stream.avail_out = (uInt)output_size;
128 stream.next_in = (z_const Bytef*)input;
129 stream.avail_in = (uInt)input_size;
130
131 result = deflate(&stream, Z_FINISH);
132 if (result == Z_STREAM_END)
133 output_size = stream.total_out;
134 result |= deflateEnd(&stream);
135 if (result != Z_STREAM_END)
136 error_exit("compress failed", result);
137
138 if (resize_output)
139 output->resize(output_size);
140 }
141
zlib_uncompress(const zlib_wrapper type,const std::string & input,const size_t output_size,std::string * output)142 void zlib_uncompress(
143 const zlib_wrapper type,
144 const std::string& input,
145 const size_t output_size,
146 std::string* output)
147 {
148 z_stream stream;
149 memset(&stream, 0, sizeof(stream));
150
151 int result = inflateInit2(&stream, zlib_stream_wrapper_type(type));
152 if (result != Z_OK)
153 error_exit("inflateInit2 failed", result);
154
155 stream.next_out = (Bytef*)string_data(output);
156 stream.avail_out = (uInt)output->size();
157 stream.next_in = (z_const Bytef*)input.data();
158 stream.avail_in = (uInt)input.size();
159
160 result = inflate(&stream, Z_FINISH);
161 if (stream.total_out != output_size)
162 result = Z_DATA_ERROR;
163 result |= inflateEnd(&stream);
164 if (result == Z_STREAM_END)
165 return;
166
167 std::string error("uncompress failed: ");
168 if (stream.msg)
169 error.append(stream.msg);
170 error_exit(error.c_str(), result);
171 }
172
verify_equal(const char * input,size_t size,std::string * output)173 void verify_equal(const char* input, size_t size, std::string* output) {
174 const char* data = string_data(output);
175 if (output->size() == size && !memcmp(data, input, size))
176 return;
177 fprintf(stderr, "uncompressed data does not match the input data\n");
178 exit(3);
179 }
180
zlib_file(const char * name,const zlib_wrapper type)181 void zlib_file(const char* name, const zlib_wrapper type) {
182 /*
183 * Read the file data.
184 */
185 const auto file = read_file_data_or_exit(name);
186 const int length = static_cast<int>(file.size);
187 const char* data = file.data.get();
188 printf("%-40s :\n", name);
189
190 /*
191 * Chop the data into blocks.
192 */
193 const int block_size = 1 << 20;
194 const int blocks = (length + block_size - 1) / block_size;
195
196 std::vector<const char*> input(blocks);
197 std::vector<size_t> input_length(blocks);
198 std::vector<std::string> compressed(blocks);
199 std::vector<std::string> output(blocks);
200
201 for (int b = 0; b < blocks; ++b) {
202 int input_start = b * block_size;
203 int input_limit = std::min<int>((b + 1) * block_size, length);
204 input[b] = data + input_start;
205 input_length[b] = input_limit - input_start;
206 }
207
208 /*
209 * Run the zlib compress/uncompress loop a few times with |repeats| to
210 * process about 10MB of data if the length is small relative to 10MB.
211 * If length is large relative to 10MB, process the data once.
212 */
213 const int mega_byte = 1024 * 1024;
214 const int repeats = (10 * mega_byte + length) / (length + 1);
215 const int runs = 5;
216 double ctime[runs];
217 double utime[runs];
218
219 for (int run = 0; run < runs; ++run) {
220 const auto now = [] { return std::chrono::steady_clock::now(); };
221
222 // Pre-grow the output buffer so we don't measure string resize time.
223 for (int b = 0; b < blocks; ++b)
224 compressed[b].resize(zlib_estimate_compressed_size(block_size));
225
226 auto start = now();
227 for (int b = 0; b < blocks; ++b)
228 for (int r = 0; r < repeats; ++r)
229 zlib_compress(type, input[b], input_length[b], &compressed[b]);
230 ctime[run] = std::chrono::duration<double>(now() - start).count();
231
232 // Compress again, resizing compressed, so we don't leave junk at the
233 // end of the compressed string that could confuse zlib_uncompress().
234 for (int b = 0; b < blocks; ++b)
235 zlib_compress(type, input[b], input_length[b], &compressed[b], true);
236
237 for (int b = 0; b < blocks; ++b)
238 output[b].resize(input_length[b]);
239
240 start = now();
241 for (int r = 0; r < repeats; ++r)
242 for (int b = 0; b < blocks; ++b)
243 zlib_uncompress(type, compressed[b], input_length[b], &output[b]);
244 utime[run] = std::chrono::duration<double>(now() - start).count();
245
246 for (int b = 0; b < blocks; ++b)
247 verify_equal(input[b], input_length[b], &output[b]);
248 }
249
250 /*
251 * Output the median/maximum compress/uncompress rates in MB/s.
252 */
253 size_t output_length = 0;
254 for (size_t i = 0; i < compressed.size(); ++i)
255 output_length += compressed[i].size();
256
257 std::sort(ctime, ctime + runs);
258 std::sort(utime, utime + runs);
259
260 double deflate_rate_med = length * repeats / mega_byte / ctime[runs / 2];
261 double inflate_rate_med = length * repeats / mega_byte / utime[runs / 2];
262 double deflate_rate_max = length * repeats / mega_byte / ctime[0];
263 double inflate_rate_max = length * repeats / mega_byte / utime[0];
264
265 // type, block size, compression ratio, etc
266 printf("%s: [b %dM] bytes %6d -> %6u %4.1f%%",
267 zlib_wrapper_name(type), block_size / (1 << 20), length,
268 static_cast<unsigned>(output_length), output_length * 100.0 / length);
269
270 // compress / uncompress median (max) rates
271 printf(" comp %5.1f (%5.1f) MB/s uncomp %5.1f (%5.1f) MB/s\n",
272 deflate_rate_med, deflate_rate_max, inflate_rate_med, inflate_rate_max);
273 }
274
275 static int argn = 1;
276
get_option(int argc,char * argv[],const char * option)277 char* get_option(int argc, char* argv[], const char* option) {
278 if (argn < argc)
279 return !strcmp(argv[argn], option) ? argv[argn++] : 0;
280 return 0;
281 }
282
get_compression(int argc,char * argv[],int * value)283 bool get_compression(int argc, char* argv[], int* value) {
284 if (argn < argc)
285 *value = atoi(argv[argn++]);
286 return *value >= 1 && *value <= 9;
287 }
288
usage_exit(const char * program)289 void usage_exit(const char* program) {
290 printf("usage: %s gzip|zlib|raw [--compression 1:9] files...\n", program);
291 exit(1);
292 }
293
main(int argc,char * argv[])294 int main(int argc, char* argv[]) {
295 zlib_wrapper type;
296 if (get_option(argc, argv, "zlib"))
297 type = kWrapperZLIB;
298 else if (get_option(argc, argv, "gzip"))
299 type = kWrapperGZIP;
300 else if (get_option(argc, argv, "raw"))
301 type = kWrapperZRAW;
302 else
303 usage_exit(argv[0]);
304
305 if (!get_option(argc, argv, "--compression"))
306 zlib_compression_level = Z_DEFAULT_COMPRESSION;
307 else if (!get_compression(argc, argv, &zlib_compression_level))
308 usage_exit(argv[0]);
309
310 if (argn >= argc)
311 usage_exit(argv[0]);
312 while (argn < argc)
313 zlib_file(argv[argn++], type);
314
315 return 0;
316 }
317