1 /*
2 * benchmark.c - a compression testing and benchmark program
3 *
4 * Copyright 2016 Eric Biggers
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation
8 * files (the "Software"), to deal in the Software without
9 * restriction, including without limitation the rights to use,
10 * copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following
13 * conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 * OTHER DEALINGS IN THE SOFTWARE.
26 */
27
28 #include <zlib.h> /* for comparison purposes */
29
30 #include "prog_util.h"
31
32 static const tchar *const optstring = T("1::2::3::4::5::6::7::8::9::C:D:ghs:VYZz");
33
34 enum wrapper {
35 NO_WRAPPER,
36 ZLIB_WRAPPER,
37 GZIP_WRAPPER,
38 };
39
40 struct compressor {
41 int level;
42 enum wrapper wrapper;
43 const struct engine *engine;
44 void *private;
45 };
46
47 struct decompressor {
48 enum wrapper wrapper;
49 const struct engine *engine;
50 void *private;
51 };
52
53 struct engine {
54 const tchar *name;
55
56 bool (*init_compressor)(struct compressor *);
57 size_t (*compress)(struct compressor *, const void *, size_t,
58 void *, size_t);
59 void (*destroy_compressor)(struct compressor *);
60
61 bool (*init_decompressor)(struct decompressor *);
62 bool (*decompress)(struct decompressor *, const void *, size_t,
63 void *, size_t);
64 void (*destroy_decompressor)(struct decompressor *);
65 };
66
67 /******************************************************************************/
68
69 static bool
libdeflate_engine_init_compressor(struct compressor * c)70 libdeflate_engine_init_compressor(struct compressor *c)
71 {
72 c->private = alloc_compressor(c->level);
73 return c->private != NULL;
74 }
75
76 static size_t
libdeflate_engine_compress(struct compressor * c,const void * in,size_t in_nbytes,void * out,size_t out_nbytes_avail)77 libdeflate_engine_compress(struct compressor *c, const void *in,
78 size_t in_nbytes, void *out, size_t out_nbytes_avail)
79 {
80 switch (c->wrapper) {
81 case ZLIB_WRAPPER:
82 return libdeflate_zlib_compress(c->private, in, in_nbytes,
83 out, out_nbytes_avail);
84 case GZIP_WRAPPER:
85 return libdeflate_gzip_compress(c->private, in, in_nbytes,
86 out, out_nbytes_avail);
87 default:
88 return libdeflate_deflate_compress(c->private, in, in_nbytes,
89 out, out_nbytes_avail);
90 }
91 }
92
93 static void
libdeflate_engine_destroy_compressor(struct compressor * c)94 libdeflate_engine_destroy_compressor(struct compressor *c)
95 {
96 libdeflate_free_compressor(c->private);
97 }
98
99 static bool
libdeflate_engine_init_decompressor(struct decompressor * d)100 libdeflate_engine_init_decompressor(struct decompressor *d)
101 {
102 d->private = alloc_decompressor();
103 return d->private != NULL;
104 }
105
106 static bool
libdeflate_engine_decompress(struct decompressor * d,const void * in,size_t in_nbytes,void * out,size_t out_nbytes)107 libdeflate_engine_decompress(struct decompressor *d, const void *in,
108 size_t in_nbytes, void *out, size_t out_nbytes)
109 {
110 switch (d->wrapper) {
111 case ZLIB_WRAPPER:
112 return !libdeflate_zlib_decompress(d->private, in, in_nbytes,
113 out, out_nbytes, NULL);
114 case GZIP_WRAPPER:
115 return !libdeflate_gzip_decompress(d->private, in, in_nbytes,
116 out, out_nbytes, NULL);
117 default:
118 return !libdeflate_deflate_decompress(d->private, in, in_nbytes,
119 out, out_nbytes, NULL);
120 }
121 }
122
123 static void
libdeflate_engine_destroy_decompressor(struct decompressor * d)124 libdeflate_engine_destroy_decompressor(struct decompressor *d)
125 {
126 libdeflate_free_decompressor(d->private);
127 }
128
129 static const struct engine libdeflate_engine = {
130 .name = T("libdeflate"),
131
132 .init_compressor = libdeflate_engine_init_compressor,
133 .compress = libdeflate_engine_compress,
134 .destroy_compressor = libdeflate_engine_destroy_compressor,
135
136 .init_decompressor = libdeflate_engine_init_decompressor,
137 .decompress = libdeflate_engine_decompress,
138 .destroy_decompressor = libdeflate_engine_destroy_decompressor,
139 };
140
141 /******************************************************************************/
142
143 static int
get_libz_window_bits(enum wrapper wrapper)144 get_libz_window_bits(enum wrapper wrapper)
145 {
146 const int windowBits = 15;
147 switch (wrapper) {
148 case ZLIB_WRAPPER:
149 return windowBits;
150 case GZIP_WRAPPER:
151 return windowBits + 16;
152 default:
153 return -windowBits;
154 }
155 }
156
157 static bool
libz_engine_init_compressor(struct compressor * c)158 libz_engine_init_compressor(struct compressor *c)
159 {
160 z_stream *z;
161
162 if (c->level > 9) {
163 msg("libz only supports up to compression level 9");
164 return false;
165 }
166
167 z = xmalloc(sizeof(*z));
168 if (z == NULL)
169 return false;
170
171 z->next_in = NULL;
172 z->avail_in = 0;
173 z->zalloc = NULL;
174 z->zfree = NULL;
175 z->opaque = NULL;
176 if (deflateInit2(z, c->level, Z_DEFLATED,
177 get_libz_window_bits(c->wrapper),
178 8, Z_DEFAULT_STRATEGY) != Z_OK)
179 {
180 msg("unable to initialize deflater");
181 free(z);
182 return false;
183 }
184
185 c->private = z;
186 return true;
187 }
188
189 static size_t
libz_engine_compress(struct compressor * c,const void * in,size_t in_nbytes,void * out,size_t out_nbytes_avail)190 libz_engine_compress(struct compressor *c, const void *in, size_t in_nbytes,
191 void *out, size_t out_nbytes_avail)
192 {
193 z_stream *z = c->private;
194
195 deflateReset(z);
196
197 z->next_in = (void *)in;
198 z->avail_in = in_nbytes;
199 z->next_out = out;
200 z->avail_out = out_nbytes_avail;
201
202 if (deflate(z, Z_FINISH) != Z_STREAM_END)
203 return 0;
204
205 return out_nbytes_avail - z->avail_out;
206 }
207
208 static void
libz_engine_destroy_compressor(struct compressor * c)209 libz_engine_destroy_compressor(struct compressor *c)
210 {
211 z_stream *z = c->private;
212
213 deflateEnd(z);
214 free(z);
215 }
216
217 static bool
libz_engine_init_decompressor(struct decompressor * d)218 libz_engine_init_decompressor(struct decompressor *d)
219 {
220 z_stream *z;
221
222 z = xmalloc(sizeof(*z));
223 if (z == NULL)
224 return false;
225
226 z->next_in = NULL;
227 z->avail_in = 0;
228 z->zalloc = NULL;
229 z->zfree = NULL;
230 z->opaque = NULL;
231 if (inflateInit2(z, get_libz_window_bits(d->wrapper)) != Z_OK) {
232 msg("unable to initialize inflater");
233 free(z);
234 return false;
235 }
236
237 d->private = z;
238 return true;
239 }
240
241 static bool
libz_engine_decompress(struct decompressor * d,const void * in,size_t in_nbytes,void * out,size_t out_nbytes)242 libz_engine_decompress(struct decompressor *d, const void *in, size_t in_nbytes,
243 void *out, size_t out_nbytes)
244 {
245 z_stream *z = d->private;
246
247 inflateReset(z);
248
249 z->next_in = (void *)in;
250 z->avail_in = in_nbytes;
251 z->next_out = out;
252 z->avail_out = out_nbytes;
253
254 return inflate(z, Z_FINISH) == Z_STREAM_END && z->avail_out == 0;
255 }
256
257 static void
libz_engine_destroy_decompressor(struct decompressor * d)258 libz_engine_destroy_decompressor(struct decompressor *d)
259 {
260 z_stream *z = d->private;
261
262 inflateEnd(z);
263 free(z);
264 }
265
266 static const struct engine libz_engine = {
267 .name = T("libz"),
268
269 .init_compressor = libz_engine_init_compressor,
270 .compress = libz_engine_compress,
271 .destroy_compressor = libz_engine_destroy_compressor,
272
273 .init_decompressor = libz_engine_init_decompressor,
274 .decompress = libz_engine_decompress,
275 .destroy_decompressor = libz_engine_destroy_decompressor,
276 };
277
278 /******************************************************************************/
279
280 static const struct engine * const all_engines[] = {
281 &libdeflate_engine,
282 &libz_engine,
283 };
284
285 #define DEFAULT_ENGINE libdeflate_engine
286
287 static const struct engine *
name_to_engine(const tchar * name)288 name_to_engine(const tchar *name)
289 {
290 size_t i;
291
292 for (i = 0; i < ARRAY_LEN(all_engines); i++)
293 if (tstrcmp(all_engines[i]->name, name) == 0)
294 return all_engines[i];
295 return NULL;
296 }
297
298 /******************************************************************************/
299
300 static bool
compressor_init(struct compressor * c,int level,enum wrapper wrapper,const struct engine * engine)301 compressor_init(struct compressor *c, int level, enum wrapper wrapper,
302 const struct engine *engine)
303 {
304 c->level = level;
305 c->wrapper = wrapper;
306 c->engine = engine;
307 return engine->init_compressor(c);
308 }
309
310 static size_t
do_compress(struct compressor * c,const void * in,size_t in_nbytes,void * out,size_t out_nbytes_avail)311 do_compress(struct compressor *c, const void *in, size_t in_nbytes,
312 void *out, size_t out_nbytes_avail)
313 {
314 return c->engine->compress(c, in, in_nbytes, out, out_nbytes_avail);
315 }
316
317 static void
compressor_destroy(struct compressor * c)318 compressor_destroy(struct compressor *c)
319 {
320 c->engine->destroy_compressor(c);
321 }
322
323 static bool
decompressor_init(struct decompressor * d,enum wrapper wrapper,const struct engine * engine)324 decompressor_init(struct decompressor *d, enum wrapper wrapper,
325 const struct engine *engine)
326 {
327 d->wrapper = wrapper;
328 d->engine = engine;
329 return engine->init_decompressor(d);
330 }
331
332 static bool
do_decompress(struct decompressor * d,const void * in,size_t in_nbytes,void * out,size_t out_nbytes)333 do_decompress(struct decompressor *d, const void *in, size_t in_nbytes,
334 void *out, size_t out_nbytes)
335 {
336 return d->engine->decompress(d, in, in_nbytes, out, out_nbytes);
337 }
338
339 static void
decompressor_destroy(struct decompressor * d)340 decompressor_destroy(struct decompressor *d)
341 {
342 d->engine->destroy_decompressor(d);
343 }
344
345 /******************************************************************************/
346
347 static void
show_available_engines(FILE * fp)348 show_available_engines(FILE *fp)
349 {
350 size_t i;
351
352 fprintf(fp, "Available ENGINEs are: ");
353 for (i = 0; i < ARRAY_LEN(all_engines); i++) {
354 fprintf(fp, "%"TS, all_engines[i]->name);
355 if (i < ARRAY_LEN(all_engines) - 1)
356 fprintf(fp, ", ");
357 }
358 fprintf(fp, ". Default is %"TS"\n", DEFAULT_ENGINE.name);
359 }
360
361 static void
show_usage(FILE * fp)362 show_usage(FILE *fp)
363 {
364 fprintf(fp,
365 "Usage: %"TS" [-LVL] [-C ENGINE] [-D ENGINE] [-ghVz] [-s SIZE] [FILE]...\n"
366 "Benchmark DEFLATE compression and decompression on the specified FILEs.\n"
367 "\n"
368 "Options:\n"
369 " -1 fastest (worst) compression\n"
370 " -6 medium compression (default)\n"
371 " -12 slowest (best) compression\n"
372 " -C ENGINE compression engine\n"
373 " -D ENGINE decompression engine\n"
374 " -g use gzip wrapper\n"
375 " -h print this help\n"
376 " -s SIZE chunk size\n"
377 " -V show version and legal information\n"
378 " -z use zlib wrapper\n"
379 "\n", program_invocation_name);
380
381 show_available_engines(fp);
382 }
383
384 static void
show_version(void)385 show_version(void)
386 {
387 printf(
388 "libdeflate compression benchmark program v" LIBDEFLATE_VERSION_STRING "\n"
389 "Copyright 2016 Eric Biggers\n"
390 "\n"
391 "This program is free software which may be modified and/or redistributed\n"
392 "under the terms of the MIT license. There is NO WARRANTY, to the extent\n"
393 "permitted by law. See the COPYING file for details.\n"
394 );
395 }
396
397
398 /******************************************************************************/
399
400 static int
do_benchmark(struct file_stream * in,void * original_buf,void * compressed_buf,void * decompressed_buf,u32 chunk_size,struct compressor * compressor,struct decompressor * decompressor)401 do_benchmark(struct file_stream *in, void *original_buf, void *compressed_buf,
402 void *decompressed_buf, u32 chunk_size,
403 struct compressor *compressor,
404 struct decompressor *decompressor)
405 {
406 u64 total_uncompressed_size = 0;
407 u64 total_compressed_size = 0;
408 u64 total_compress_time = 0;
409 u64 total_decompress_time = 0;
410 ssize_t ret;
411
412 while ((ret = xread(in, original_buf, chunk_size)) > 0) {
413 u32 original_size = ret;
414 u32 compressed_size;
415 u64 start_time;
416 bool ok;
417
418 total_uncompressed_size += original_size;
419
420 /* Compress the chunk of data. */
421 start_time = timer_ticks();
422 compressed_size = do_compress(compressor,
423 original_buf,
424 original_size,
425 compressed_buf,
426 original_size - 1);
427 total_compress_time += timer_ticks() - start_time;
428
429 if (compressed_size) {
430 /* Successfully compressed the chunk of data. */
431
432 /* Decompress the data we just compressed and compare
433 * the result with the original. */
434 start_time = timer_ticks();
435 ok = do_decompress(decompressor,
436 compressed_buf, compressed_size,
437 decompressed_buf, original_size);
438 total_decompress_time += timer_ticks() - start_time;
439
440 if (!ok) {
441 msg("%"TS": failed to decompress data",
442 in->name);
443 return -1;
444 }
445
446 if (memcmp(original_buf, decompressed_buf,
447 original_size) != 0)
448 {
449 msg("%"TS": data did not decompress to "
450 "original", in->name);
451 return -1;
452 }
453
454 total_compressed_size += compressed_size;
455 } else {
456 /* Compression did not make the chunk smaller. */
457 total_compressed_size += original_size;
458 }
459 }
460
461 if (ret < 0)
462 return ret;
463
464 if (total_uncompressed_size == 0) {
465 printf("\tFile was empty.\n");
466 return 0;
467 }
468
469 if (total_compress_time == 0)
470 total_compress_time = 1;
471 if (total_decompress_time == 0)
472 total_decompress_time = 1;
473
474 printf("\tCompressed %"PRIu64 " => %"PRIu64" bytes (%u.%03u%%)\n",
475 total_uncompressed_size, total_compressed_size,
476 (unsigned int)(total_compressed_size * 100 /
477 total_uncompressed_size),
478 (unsigned int)(total_compressed_size * 100000 /
479 total_uncompressed_size % 1000));
480 printf("\tCompression time: %"PRIu64" ms (%"PRIu64" MB/s)\n",
481 timer_ticks_to_ms(total_compress_time),
482 timer_MB_per_s(total_uncompressed_size, total_compress_time));
483 printf("\tDecompression time: %"PRIu64" ms (%"PRIu64" MB/s)\n",
484 timer_ticks_to_ms(total_decompress_time),
485 timer_MB_per_s(total_uncompressed_size, total_decompress_time));
486
487 return 0;
488 }
489
490 int
tmain(int argc,tchar * argv[])491 tmain(int argc, tchar *argv[])
492 {
493 u32 chunk_size = 1048576;
494 int level = 6;
495 enum wrapper wrapper = NO_WRAPPER;
496 const struct engine *compress_engine = &DEFAULT_ENGINE;
497 const struct engine *decompress_engine = &DEFAULT_ENGINE;
498 void *original_buf = NULL;
499 void *compressed_buf = NULL;
500 void *decompressed_buf = NULL;
501 struct compressor compressor;
502 struct decompressor decompressor;
503 tchar *default_file_list[] = { NULL };
504 int opt_char;
505 int i;
506 int ret;
507
508 program_invocation_name = get_filename(argv[0]);
509
510 while ((opt_char = tgetopt(argc, argv, optstring)) != -1) {
511 switch (opt_char) {
512 case '1':
513 case '2':
514 case '3':
515 case '4':
516 case '5':
517 case '6':
518 case '7':
519 case '8':
520 case '9':
521 level = parse_compression_level(opt_char, toptarg);
522 if (level == 0)
523 return 1;
524 break;
525 case 'C':
526 compress_engine = name_to_engine(toptarg);
527 if (compress_engine == NULL) {
528 msg("invalid compression engine: \"%"TS"\"", toptarg);
529 show_available_engines(stderr);
530 return 1;
531 }
532 break;
533 case 'D':
534 decompress_engine = name_to_engine(toptarg);
535 if (decompress_engine == NULL) {
536 msg("invalid decompression engine: \"%"TS"\"", toptarg);
537 show_available_engines(stderr);
538 return 1;
539 }
540 break;
541 case 'g':
542 wrapper = GZIP_WRAPPER;
543 break;
544 case 'h':
545 show_usage(stdout);
546 return 0;
547 case 's':
548 chunk_size = tstrtoul(toptarg, NULL, 10);
549 if (chunk_size == 0) {
550 msg("invalid chunk size: \"%"TS"\"", toptarg);
551 return 1;
552 }
553 break;
554 case 'V':
555 show_version();
556 return 0;
557 case 'Y': /* deprecated, use '-C libz' instead */
558 compress_engine = &libz_engine;
559 break;
560 case 'Z': /* deprecated, use '-D libz' instead */
561 decompress_engine = &libz_engine;
562 break;
563 case 'z':
564 wrapper = ZLIB_WRAPPER;
565 break;
566 default:
567 show_usage(stderr);
568 return 1;
569 }
570 }
571
572 argc -= toptind;
573 argv += toptind;
574
575 original_buf = xmalloc(chunk_size);
576 compressed_buf = xmalloc(chunk_size - 1);
577 decompressed_buf = xmalloc(chunk_size);
578
579 ret = -1;
580 if (original_buf == NULL || compressed_buf == NULL ||
581 decompressed_buf == NULL)
582 goto out0;
583
584 if (!compressor_init(&compressor, level, wrapper, compress_engine))
585 goto out0;
586
587 if (!decompressor_init(&decompressor, wrapper, decompress_engine))
588 goto out1;
589
590 if (argc == 0) {
591 argv = default_file_list;
592 argc = ARRAY_LEN(default_file_list);
593 } else {
594 for (i = 0; i < argc; i++)
595 if (argv[i][0] == '-' && argv[i][1] == '\0')
596 argv[i] = NULL;
597 }
598
599 printf("Benchmarking DEFLATE compression:\n");
600 printf("\tCompression level: %d\n", level);
601 printf("\tChunk size: %"PRIu32"\n", chunk_size);
602 printf("\tWrapper: %s\n",
603 wrapper == NO_WRAPPER ? "None" :
604 wrapper == ZLIB_WRAPPER ? "zlib" : "gzip");
605 printf("\tCompression engine: %"TS"\n", compress_engine->name);
606 printf("\tDecompression engine: %"TS"\n", decompress_engine->name);
607
608 for (i = 0; i < argc; i++) {
609 struct file_stream in;
610
611 ret = xopen_for_read(argv[i], true, &in);
612 if (ret != 0)
613 goto out2;
614
615 printf("Processing %"TS"...\n", in.name);
616
617 ret = do_benchmark(&in, original_buf, compressed_buf,
618 decompressed_buf, chunk_size, &compressor,
619 &decompressor);
620 xclose(&in);
621 if (ret != 0)
622 goto out2;
623 }
624 ret = 0;
625 out2:
626 decompressor_destroy(&decompressor);
627 out1:
628 compressor_destroy(&compressor);
629 out0:
630 free(decompressed_buf);
631 free(compressed_buf);
632 free(original_buf);
633 return -ret;
634 }
635