| /* |
| * Copyright 2018 The Chromium Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the Chromium source repository LICENSE file. |
| * |
| * A benchmark test harness for measuring decoding performance of gzip or zlib |
| * (deflate) encoded compressed data. Given a file containing any data, encode |
| * (compress) it into gzip or zlib format and then decode (uncompress). Output |
| * the median and maximum encoding and decoding rates in MB/s. |
| * |
| * Raw deflate (no gzip or zlib stream wrapper) mode is also supported. Select |
| * it with the [raw] argument. Use the [gzip] [zlib] arguments to select those |
| * stream wrappers. |
| * |
| * Note this code can be compiled outside of the Chromium build system against |
| * the system zlib (-lz) with g++ or clang++ as follows: |
| * |
| * g++|clang++ -O3 -Wall -std=c++11 -lstdc++ -lz zlib_bench.cc |
| */ |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <fstream> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <memory.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include "zlib.h" |
| |
| void error_exit(const char* error, int code) { |
| fprintf(stderr, "%s (%d)\n", error, code); |
| exit(code); |
| } |
| |
| inline char* string_data(std::string* s) { |
| return s->empty() ? 0 : &*s->begin(); |
| } |
| |
| struct Data { |
| Data(size_t s) { data.reset(new (std::nothrow) char[size = s]); } |
| std::unique_ptr<char[]> data; |
| size_t size; |
| }; |
| |
| Data read_file_data_or_exit(const char* name) { |
| std::ifstream file(name, std::ios::in | std::ios::binary); |
| if (!file) { |
| perror(name); |
| exit(1); |
| } |
| |
| file.seekg(0, std::ios::end); |
| Data data(file.tellg()); |
| file.seekg(0, std::ios::beg); |
| |
| if (file && data.data) |
| file.read(data.data.get(), data.size); |
| |
| if (!file || !data.data || !data.size) { |
| perror((std::string("failed: reading ") + name).c_str()); |
| exit(1); |
| } |
| |
| return data; |
| } |
| |
| size_t zlib_estimate_compressed_size(size_t input_size) { |
| return compressBound(input_size); |
| } |
| |
| enum zlib_wrapper { |
| kWrapperNONE, |
| kWrapperZLIB, |
| kWrapperGZIP, |
| kWrapperZRAW, |
| }; |
| |
| inline int zlib_stream_wrapper_type(zlib_wrapper type) { |
| if (type == kWrapperZLIB) // zlib DEFLATE stream wrapper |
| return MAX_WBITS; |
| if (type == kWrapperGZIP) // gzip DEFLATE stream wrapper |
| return MAX_WBITS + 16; |
| if (type == kWrapperZRAW) // no wrapper, use raw DEFLATE |
| return -MAX_WBITS; |
| error_exit("bad wrapper type", int(type)); |
| return 0; |
| } |
| |
| const char* zlib_wrapper_name(zlib_wrapper type) { |
| if (type == kWrapperZLIB) |
| return "ZLIB"; |
| if (type == kWrapperGZIP) |
| return "GZIP"; |
| if (type == kWrapperZRAW) |
| return "RAW"; |
| error_exit("bad wrapper type", int(type)); |
| return 0; |
| } |
| |
| static int zlib_compression_level; |
| |
| void zlib_compress( |
| const zlib_wrapper type, |
| const char* input, |
| const size_t input_size, |
| std::string* output, |
| bool resize_output = false) |
| { |
| if (resize_output) |
| output->resize(zlib_estimate_compressed_size(input_size)); |
| size_t output_size = output->size(); |
| |
| z_stream stream; |
| memset(&stream, 0, sizeof(stream)); |
| |
| int result = deflateInit2(&stream, zlib_compression_level, Z_DEFLATED, |
| zlib_stream_wrapper_type(type), MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); |
| if (result != Z_OK) |
| error_exit("deflateInit2 failed", result); |
| |
| stream.next_out = (Bytef*)string_data(output); |
| stream.avail_out = (uInt)output_size; |
| stream.next_in = (z_const Bytef*)input; |
| stream.avail_in = (uInt)input_size; |
| |
| result = deflate(&stream, Z_FINISH); |
| if (result == Z_STREAM_END) |
| output_size = stream.total_out; |
| result |= deflateEnd(&stream); |
| if (result != Z_STREAM_END) |
| error_exit("compress failed", result); |
| |
| if (resize_output) |
| output->resize(output_size); |
| } |
| |
| void zlib_uncompress( |
| const zlib_wrapper type, |
| const std::string& input, |
| const size_t output_size, |
| std::string* output) |
| { |
| z_stream stream; |
| memset(&stream, 0, sizeof(stream)); |
| |
| int result = inflateInit2(&stream, zlib_stream_wrapper_type(type)); |
| if (result != Z_OK) |
| error_exit("inflateInit2 failed", result); |
| |
| stream.next_out = (Bytef*)string_data(output); |
| stream.avail_out = (uInt)output->size(); |
| stream.next_in = (z_const Bytef*)input.data(); |
| stream.avail_in = (uInt)input.size(); |
| |
| result = inflate(&stream, Z_FINISH); |
| if (stream.total_out != output_size) |
| result = Z_DATA_ERROR; |
| result |= inflateEnd(&stream); |
| if (result == Z_STREAM_END) |
| return; |
| |
| std::string error("uncompress failed: "); |
| if (stream.msg) |
| error.append(stream.msg); |
| error_exit(error.c_str(), result); |
| } |
| |
| void verify_equal(const char* input, size_t size, std::string* output) { |
| const char* data = string_data(output); |
| if (output->size() == size && !memcmp(data, input, size)) |
| return; |
| fprintf(stderr, "uncompressed data does not match the input data\n"); |
| exit(3); |
| } |
| |
| void zlib_file(const char* name, const zlib_wrapper type) { |
| /* |
| * Read the file data. |
| */ |
| const auto file = read_file_data_or_exit(name); |
| const int length = static_cast<int>(file.size); |
| const char* data = file.data.get(); |
| printf("%-40s :\n", name); |
| |
| /* |
| * Chop the data into blocks. |
| */ |
| const int block_size = 1 << 20; |
| const int blocks = (length + block_size - 1) / block_size; |
| |
| std::vector<const char*> input(blocks); |
| std::vector<size_t> input_length(blocks); |
| std::vector<std::string> compressed(blocks); |
| std::vector<std::string> output(blocks); |
| |
| for (int b = 0; b < blocks; ++b) { |
| int input_start = b * block_size; |
| int input_limit = std::min<int>((b + 1) * block_size, length); |
| input[b] = data + input_start; |
| input_length[b] = input_limit - input_start; |
| } |
| |
| /* |
| * Run the zlib compress/uncompress loop a few times with |repeats| to |
| * process about 10MB of data if the length is small relative to 10MB. |
| * If length is large relative to 10MB, process the data once. |
| */ |
| const int mega_byte = 1024 * 1024; |
| const int repeats = (10 * mega_byte + length) / (length + 1); |
| const int runs = 5; |
| double ctime[runs]; |
| double utime[runs]; |
| |
| for (int run = 0; run < runs; ++run) { |
| const auto now = [] { return std::chrono::steady_clock::now(); }; |
| |
| // Pre-grow the output buffer so we don't measure string resize time. |
| for (int b = 0; b < blocks; ++b) |
| compressed[b].resize(zlib_estimate_compressed_size(block_size)); |
| |
| auto start = now(); |
| for (int b = 0; b < blocks; ++b) |
| for (int r = 0; r < repeats; ++r) |
| zlib_compress(type, input[b], input_length[b], &compressed[b]); |
| ctime[run] = std::chrono::duration<double>(now() - start).count(); |
| |
| // Compress again, resizing compressed, so we don't leave junk at the |
| // end of the compressed string that could confuse zlib_uncompress(). |
| for (int b = 0; b < blocks; ++b) |
| zlib_compress(type, input[b], input_length[b], &compressed[b], true); |
| |
| for (int b = 0; b < blocks; ++b) |
| output[b].resize(input_length[b]); |
| |
| start = now(); |
| for (int r = 0; r < repeats; ++r) |
| for (int b = 0; b < blocks; ++b) |
| zlib_uncompress(type, compressed[b], input_length[b], &output[b]); |
| utime[run] = std::chrono::duration<double>(now() - start).count(); |
| |
| for (int b = 0; b < blocks; ++b) |
| verify_equal(input[b], input_length[b], &output[b]); |
| } |
| |
| /* |
| * Output the median/maximum compress/uncompress rates in MB/s. |
| */ |
| size_t output_length = 0; |
| for (size_t i = 0; i < compressed.size(); ++i) |
| output_length += compressed[i].size(); |
| |
| std::sort(ctime, ctime + runs); |
| std::sort(utime, utime + runs); |
| |
| double deflate_rate_med = length * repeats / mega_byte / ctime[runs / 2]; |
| double inflate_rate_med = length * repeats / mega_byte / utime[runs / 2]; |
| double deflate_rate_max = length * repeats / mega_byte / ctime[0]; |
| double inflate_rate_max = length * repeats / mega_byte / utime[0]; |
| |
| // type, block size, compression ratio, etc |
| printf("%s: [b %dM] bytes %6d -> %6u %4.1f%%", |
| zlib_wrapper_name(type), block_size / (1 << 20), length, |
| static_cast<unsigned>(output_length), output_length * 100.0 / length); |
| |
| // compress / uncompress median (max) rates |
| printf(" comp %5.1f (%5.1f) MB/s uncomp %5.1f (%5.1f) MB/s\n", |
| deflate_rate_med, deflate_rate_max, inflate_rate_med, inflate_rate_max); |
| } |
| |
| static int argn = 1; |
| |
| char* get_option(int argc, char* argv[], const char* option) { |
| if (argn < argc) |
| return !strcmp(argv[argn], option) ? argv[argn++] : 0; |
| return 0; |
| } |
| |
| bool get_compression(int argc, char* argv[], int* value) { |
| if (argn < argc) |
| *value = atoi(argv[argn++]); |
| return *value >= 1 && *value <= 9; |
| } |
| |
| void usage_exit(const char* program) { |
| printf("usage: %s gzip|zlib|raw [--compression 1:9] files...\n", program); |
| exit(1); |
| } |
| |
| int main(int argc, char* argv[]) { |
| zlib_wrapper type; |
| if (get_option(argc, argv, "zlib")) |
| type = kWrapperZLIB; |
| else if (get_option(argc, argv, "gzip")) |
| type = kWrapperGZIP; |
| else if (get_option(argc, argv, "raw")) |
| type = kWrapperZRAW; |
| else |
| usage_exit(argv[0]); |
| |
| if (!get_option(argc, argv, "--compression")) |
| zlib_compression_level = Z_DEFAULT_COMPRESSION; |
| else if (!get_compression(argc, argv, &zlib_compression_level)) |
| usage_exit(argv[0]); |
| |
| if (argn >= argc) |
| usage_exit(argv[0]); |
| while (argn < argc) |
| zlib_file(argv[argn++], type); |
| |
| return 0; |
| } |