diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dc9d49..df7231e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,12 +64,6 @@ else() add_definitions(-DUNSAFE_DECOMPRESSION=0) endif() -option(BUILD_EXAMPLES "Build the example programs" OFF) -if(BUILD_EXAMPLES) - add_executable(benchmark examples/benchmark.c) - target_link_libraries(benchmark deflate) -endif() - add_library(deflate SHARED ${LIB_SOURCES}) add_library(deflatestatic STATIC ${LIB_SOURCES}) @@ -81,3 +75,7 @@ install(TARGETS deflate deflatestatic ARCHIVE DESTINATION "${CMAKE_INSTALL_PREFIX}/lib") install(FILES libdeflate.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include") + +option(BUILD_BENCHMARK "Build benchmark program" OFF) +add_executable(benchmark test/benchmark.c) +target_link_libraries(benchmark deflate -lz) diff --git a/examples/benchmark.c b/examples/benchmark.c deleted file mode 100644 index edc0482..0000000 --- a/examples/benchmark.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * benchmark.c - A compression testing and benchmark program. - * - * The author dedicates this file to the public domain. - * You can do whatever you want with this file. - */ - -#include - -#ifdef __WIN32__ -# include -#else -# define _FILE_OFFSET_BITS 64 -# define O_BINARY 0 -# define _POSIX_C_SOURCE 199309L -# include -#endif - -#include -#include -#include -#include -#include -#include -#include - -static uint64_t -current_time(void) -{ -#ifdef __WIN32__ -# define TIME_UNIT_PER_MS 10000 - LARGE_INTEGER time; - QueryPerformanceCounter(&time); - return time.QuadPart; -#else -# define TIME_UNIT_PER_MS 1000000 - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (1000000000ULL * ts.tv_sec) + ts.tv_nsec; -#endif -} - -static int -do_benchmark(int fd, char *ubuf1, char *ubuf2, - char *cbuf, uint32_t max_chunk_size, - struct deflate_compressor *compressor, - struct deflate_decompressor *decompressor) -{ - uint64_t usize_total = 0; - uint64_t csize_total = 0; - uint64_t compress_time_total = 0; - uint64_t decompress_time_total = 0; - - for (;;) { - char *p = ubuf1; - ssize_t bytes_read; - size_t usize; - size_t csize; - bool ok; - uint64_t start_time; - - /* Read the next chunk of data. */ - do { - bytes_read = read(fd, p, ubuf1 + max_chunk_size - p); - if (bytes_read < 0) { - fprintf(stderr, "ERROR: Read error: %s\n", - strerror(errno)); - return 1; - } - p += bytes_read; - } while (bytes_read != 0 && p != ubuf1 + max_chunk_size); - - usize = p - ubuf1; - - if (usize == 0) /* End of file? */ - break; - - /* Compress the chunk of data. */ - usize_total += usize; - start_time = current_time(); - csize = deflate_compress(compressor, ubuf1, usize, - cbuf, usize - 1); - compress_time_total += current_time() - start_time; - - if (csize) { - /* Successfully compressed the chunk of data. */ - csize_total += csize; - - /* Decompress the data we just compressed and compare - * the result with the original. */ - start_time = current_time(); - ok = deflate_decompress(decompressor, cbuf, csize, - ubuf2, usize); - decompress_time_total += current_time() - start_time; - if (!ok) { - fprintf(stderr, "ERROR: Failed to " - "decompress data\n"); - return 1; - } - - if (memcmp(ubuf1, ubuf2, usize)) { - fprintf(stderr, "ERROR: Data did not " - "decompress to original\n"); - return 1; - } - } else { - /* Chunk of data did not compress to less than its - * original size. */ - csize_total += usize; - } - } - - - if (usize_total == 0) { - printf("\tEmpty input.\n"); - return 0; - } - - if (compress_time_total == 0) - compress_time_total++; - if (decompress_time_total == 0) - decompress_time_total++; - - printf("\tCompressed %"PRIu64 " => %"PRIu64" bytes (%u.%u%%)\n", - usize_total, csize_total, - (unsigned int)(csize_total * 100 / usize_total), - (unsigned int)(csize_total * 100000 / usize_total % 1000)); - printf("\tCompression time: %"PRIu64" ms (%"PRIu64" MB/s)\n", - compress_time_total / TIME_UNIT_PER_MS, - 1000 * usize_total / compress_time_total); - printf("\tDecompression time: %"PRIu64" ms (%"PRIu64" MB/s)\n", - decompress_time_total / TIME_UNIT_PER_MS, - 1000 * usize_total / decompress_time_total); - return 0; -} - -int -main(int argc, char **argv) -{ - const char *filename; - uint32_t chunk_size = 32768; - unsigned int compression_level = 6; - char *ubuf1 = NULL; - char *ubuf2 = NULL; - char *cbuf = NULL; - struct deflate_compressor *compressor = NULL; - struct deflate_decompressor *decompressor = NULL; - int fd = -1; - int ret; - - if (argc < 2 || argc > 5) { - fprintf(stderr, "Usage: %s FILE [CHUNK_SIZE [LEVEL]]]\n", argv[0]); - ret = 2; - goto out; - } - - filename = argv[1]; - - if (argc >= 3) - chunk_size = strtoul(argv[2], NULL, 10); - - if (argc >= 4) - compression_level = strtoul(argv[3], NULL, 10); - - printf("DEFLATE compression with %"PRIu32" byte chunks (level %u)\n", - chunk_size, compression_level); - - compressor = deflate_alloc_compressor(compression_level); - if (!compressor) { - fprintf(stderr, "ERROR: Failed to create compressor\n"); - ret = 1; - goto out; - } - - decompressor = deflate_alloc_decompressor(); - if (!decompressor) { - fprintf(stderr, "ERROR: Failed to create decompressor\n"); - ret = 1; - goto out; - } - - ubuf1 = malloc(chunk_size); - ubuf2 = malloc(chunk_size); - cbuf = malloc(chunk_size - 1); - - if (!ubuf1 || !ubuf2 || !cbuf) { - fprintf(stderr, "ERROR: Insufficient memory\n"); - ret = 1; - goto out; - } - - fd = open(filename, O_RDONLY | O_BINARY); - if (fd < 0) { - fprintf(stderr, "ERROR: Can't open \"%s\" for reading: %s\n", - filename, strerror(errno)); - ret = 1; - goto out; - } - - ret = do_benchmark(fd, ubuf1, ubuf2, cbuf, chunk_size, - compressor, decompressor); -out: - close(fd); - free(cbuf); - free(ubuf2); - free(ubuf1); - deflate_free_decompressor(decompressor); - deflate_free_compressor(compressor); - return ret; -} diff --git a/test/benchmark.c b/test/benchmark.c new file mode 100644 index 0000000..205060e --- /dev/null +++ b/test/benchmark.c @@ -0,0 +1,458 @@ +/* + * benchmark.c - A compression testing and benchmark program. + * + * The author dedicates this file to the public domain. + * You can do whatever you want with this file. + */ + + +#define _FILE_OFFSET_BITS 64 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static void +usage(FILE *fp) +{ + static const char * const str = +"Usage: benchmark [FILE...]\n" +"\n" +"A compression and decompression benchmark and testing program.\n" +"Benchmarks are run on each FILE specified, or stdin if no file is specified.\n" +"\n" +"Options:\n" +" -s, --chunk-size=SIZE chunk size\n" +" -l, --level=LEVEL compression level [0-9]\n" +" -1 fastest\n" +" -9 slowest\n" +" -z, --zlib use zlib wrapper\n" +" -g, --gzip use gzip wrapper\n" +" -Y, --compress-with-libz compress with libz, not libdeflate\n" +" -Z, --decompress-with-libz decompress with libz, not libdeflate\n" +" -h, --help print this help\n" + ; + + fputs(str, fp); +} + +static void +fatal_error(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + fprintf(stderr, "ERROR: "); + vfprintf(stderr, fmt, va); + fprintf(stderr, "\n"); + va_end(va); + + exit(1); +} + +#define ASSERT(expr, fmt, ...) \ +{ \ + if (!(expr)) \ + fatal_error((fmt), ## __VA_ARGS__); \ +} + +enum wrapper { + NO_WRAPPER, + ZLIB_WRAPPER, + GZIP_WRAPPER, +}; + +struct compressor { + void *private; + size_t (*compress)(void *, const void *, size_t, void *, size_t); + void (*free_private)(void *); +}; + +static size_t +libz_compress(void *private, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail) +{ + z_stream *z = private; + + deflateReset(z); + + z->next_in = (void *)in; + z->avail_in = in_nbytes; + z->next_out = out; + z->avail_out = out_nbytes_avail; + + if (deflate(z, Z_FINISH) != Z_STREAM_END) + return 0; + + return out_nbytes_avail - z->avail_out; +} + +static void +libz_free_compressor_private(void *private) +{ + deflateEnd((z_stream *)private); + free(private); +} + +static int +get_libz_window_bits(enum wrapper wrapper) +{ + const int windowBits = 15; + switch (wrapper) { + case NO_WRAPPER: + return -windowBits; + case ZLIB_WRAPPER: + return windowBits; + case GZIP_WRAPPER: + return windowBits + 16; + } + return windowBits; +} + +static void +compressor_init(struct compressor *c, int level, + enum wrapper wrapper, bool use_libz) +{ + if (use_libz) { + z_stream *z; + int zstatus; + + z = malloc(sizeof(z_stream)); + ASSERT(z != NULL, "out of memory"); + + c->private = z; + + z->next_in = NULL; + z->avail_in = 0; + z->zalloc = NULL; + z->zfree = NULL; + z->opaque = NULL; + zstatus = deflateInit2(z, level, Z_DEFLATED, + get_libz_window_bits(wrapper), + 8, Z_DEFAULT_STRATEGY); + ASSERT(zstatus == Z_OK, "unable to initialize deflater"); + c->compress = libz_compress; + c->free_private = libz_free_compressor_private; + } else { + c->private = deflate_alloc_compressor(level); + ASSERT(c->private != NULL, "failed to allocate compressor"); + switch (wrapper) { + case NO_WRAPPER: + c->compress = (void *)deflate_compress; + break; + case ZLIB_WRAPPER: + c->compress = (void *)zlib_compress; + break; + case GZIP_WRAPPER: + c->compress = (void *)gzip_compress; + break; + } + c->free_private = (void *)deflate_free_compressor; + } +} + +static size_t +do_compress(struct compressor *c, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes_avail) +{ + return (*c->compress)(c->private, in, in_nbytes, out, out_nbytes_avail); +} + +static void +compressor_destroy(struct compressor *c) +{ + (*c->free_private)(c->private); +} + +struct decompressor { + void *private; + bool (*decompress)(void *, const void *, size_t, void *, size_t); + void (*free_private)(void *); +}; + +static bool +libz_decompress(void *private, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes) +{ + z_stream *z = private; + + inflateReset(z); + + z->next_in = (void *)in; + z->avail_in = in_nbytes; + z->next_out = out; + z->avail_out = out_nbytes; + + return (inflate(z, Z_FINISH) == Z_STREAM_END && z->avail_out == 0); +} + +static void +libz_free_decompressor_private(void *private) +{ + inflateEnd((z_stream *)private); + free(private); +} + +static void +decompressor_init(struct decompressor *d, enum wrapper wrapper, bool use_libz) +{ + if (use_libz) { + z_stream *z; + int zstatus; + + z = malloc(sizeof(z_stream)); + ASSERT(z != NULL, "out of memory"); + + d->private = z; + + z->next_in = NULL; + z->avail_in = 0; + z->zalloc = NULL; + z->zfree = NULL; + z->opaque = NULL; + zstatus = inflateInit2(z, get_libz_window_bits(wrapper)); + ASSERT(zstatus == Z_OK, "failed to initialize inflater"); + + d->decompress = libz_decompress; + d->free_private = libz_free_decompressor_private; + } else { + d->private = deflate_alloc_decompressor(); + ASSERT(d->private != NULL, "out of memory"); + switch (wrapper) { + case NO_WRAPPER: + d->decompress = (void *)deflate_decompress; + break; + case ZLIB_WRAPPER: + d->decompress = (void *)zlib_decompress; + break; + case GZIP_WRAPPER: + d->decompress = (void *)gzip_decompress; + break; + } + d->free_private = (void *)deflate_free_decompressor; + } +} + +static bool +do_decompress(struct decompressor *d, const void *in, size_t in_nbytes, + void *out, size_t out_nbytes) +{ + return (*d->decompress)(d->private, in, in_nbytes, out, out_nbytes); +} + +static void +decompressor_destroy(struct decompressor *d) +{ + (*d->free_private)(d->private); +} + +#define TIME_UNIT_PER_MS 1000000 +static uint64_t +current_time(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((uint64_t)1000000000 * ts.tv_sec) + ts.tv_nsec; +} + +static void +do_benchmark(int fd, char *ubuf1, char *ubuf2, + char *cbuf, uint32_t max_chunk_size, + struct compressor *c, struct decompressor *d) +{ + uint64_t usize_total = 0; + uint64_t csize_total = 0; + uint64_t compress_time_total = 0; + uint64_t decompress_time_total = 0; + + for (;;) { + char *p = ubuf1; + ssize_t bytes_read; + size_t usize; + size_t csize; + uint64_t start_time; + bool ok; + + /* Read the next chunk of data. */ + do { + bytes_read = read(fd, p, ubuf1 + max_chunk_size - p); + ASSERT(bytes_read >= 0, "read error"); + p += bytes_read; + } while (bytes_read != 0 && p != ubuf1 + max_chunk_size); + + usize = p - ubuf1; + + if (usize == 0) /* End of file? */ + break; + + /* Compress the chunk of data. */ + usize_total += usize; + start_time = current_time(); + csize = do_compress(c, ubuf1, usize, cbuf, usize - 1); + compress_time_total += current_time() - start_time; + + if (csize) { + /* Successfully compressed the chunk of data. */ + csize_total += csize; + + /* Decompress the data we just compressed and compare + * the result with the original. */ + start_time = current_time(); + + ok = do_decompress(d, cbuf, csize, ubuf2, usize); + + decompress_time_total += current_time() - start_time; + + ASSERT(ok, "failed to decompress data"); + + ASSERT(!memcmp(ubuf1, ubuf2, usize), + "data did not decompress to original"); + } else { + /* Chunk of data did not compress to less than its + * original size. */ + csize_total += usize; + } + } + + + if (usize_total == 0) { + printf("\tEmpty input.\n"); + return; + } + + if (compress_time_total == 0) + compress_time_total++; + if (decompress_time_total == 0) + decompress_time_total++; + + printf("\tCompressed %"PRIu64 " => %"PRIu64" bytes (%u.%u%%)\n", + usize_total, csize_total, + (unsigned int)(csize_total * 100 / usize_total), + (unsigned int)(csize_total * 100000 / usize_total % 1000)); + printf("\tCompression time: %"PRIu64" ms (%"PRIu64" MB/s)\n", + compress_time_total / TIME_UNIT_PER_MS, + 1000 * usize_total / compress_time_total); + printf("\tDecompression time: %"PRIu64" ms (%"PRIu64" MB/s)\n", + decompress_time_total / TIME_UNIT_PER_MS, + 1000 * usize_total / decompress_time_total); +} + +static const char *const optstring = "s:l:0123456789gzYZh"; +static const struct option longopts[] = { + {"chunk-size", required_argument, NULL, 's'}, + {"level", required_argument, NULL, 'l'}, + {"zlib", no_argument, NULL, 'z'}, + {"gzip", no_argument, NULL, 'g'}, + {"compress-with-libz", no_argument, NULL, 'Y'}, + {"decompress-with-libz", no_argument, NULL, 'Z'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0}, +}; + +int +main(int argc, char **argv) +{ + uint32_t chunk_size = 32768; + int level = 6; + enum wrapper wrapper = NO_WRAPPER; + bool compress_with_libz = false; + bool decompress_with_libz = false; + char *ubuf1; + char *ubuf2; + char *cbuf; + struct compressor c; + struct decompressor d; + int opt_char; + + while ((opt_char = + getopt_long(argc, argv, optstring, longopts, NULL)) != -1) + { + switch (opt_char) { + case 's': + chunk_size = strtoul(optarg, NULL, 10); + break; + case 'l': + level = strtoul(optarg, NULL, 10); + break; + case '1' ... '9': + level = opt_char - '0'; + break; + case 'z': + wrapper = ZLIB_WRAPPER; + break; + case 'g': + wrapper = GZIP_WRAPPER; + break; + case 'Y': + compress_with_libz = true; + break; + case 'Z': + decompress_with_libz = true; + break; + case 'h': + usage(stdout); + return 0; + default: + usage(stderr); + return 1; + } + } + + argc -= optind; + argv += optind; + + printf("Benchmarking DEFLATE compression:\n"); + printf("\tCompression level: %d\n", level); + printf("\tChunk size: %"PRIu32"\n", chunk_size); + printf("\tWrapper: %s\n", + wrapper == NO_WRAPPER ? "None" : + wrapper == ZLIB_WRAPPER ? "zlib" : "gzip"); + printf("\tCompression engine: %s\n", + compress_with_libz ? "zlib" : "libdeflate"); + printf("\tDecompression engine: %s\n", + decompress_with_libz ? "zlib" : "libdeflate"); + + ubuf1 = malloc(chunk_size); + ubuf2 = malloc(chunk_size); + cbuf = malloc(chunk_size - 1); + + ASSERT(ubuf1 != NULL && ubuf2 != NULL && cbuf != NULL, + "out of memory"); + + compressor_init(&c, level, wrapper, compress_with_libz); + decompressor_init(&d, wrapper, decompress_with_libz); + + if (argc == 0) { + printf("Reading from stdin...\n"); + do_benchmark(STDIN_FILENO, ubuf1, ubuf2, + cbuf, chunk_size, &c, &d); + } else { + for (int i = 0; i < argc; i++) { + printf("Processing \"%s\"...\n", argv[i]); + int fd = open(argv[i], O_RDONLY); + ASSERT(fd >= 0, + "Can't open \"%s\" for reading: %s\n", + argv[i], strerror(errno)); + do_benchmark(fd, ubuf1, ubuf2, cbuf, chunk_size, &c, &d); + close(fd); + } + } + + decompressor_destroy(&d); + compressor_destroy(&c); + free(cbuf); + free(ubuf2); + free(ubuf1); + return 0; +}