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