]> git.ipfire.org Git - thirdparty/zlib-ng.git/commitdiff
Add new benchmark inflate_nocrc. This lets us benchmark just the
authorHans Kristian Rosbach <hk-git@circlestorm.org>
Sat, 27 Dec 2025 21:51:22 +0000 (22:51 +0100)
committerHans Kristian Rosbach <hk-github@circlestorm.org>
Wed, 7 Jan 2026 06:46:48 +0000 (07:46 +0100)
inflate process more accurately. Also adds a new shared function for
generating highly compressible data that avoids very long matches.

test/benchmarks/CMakeLists.txt
test/benchmarks/benchmark_inflate.cc [new file with mode: 0644]
test/benchmarks/compressible_data_p.h [new file with mode: 0644]

index c9fe520ad3248868dfb4c678ab147e2d8fa060ff..4c206902ff6178c6070517be799597b9676d7e8b 100644 (file)
@@ -46,6 +46,7 @@ add_executable(benchmark_zlib
     benchmark_crc32.cc
     benchmark_crc32_copy.cc
     benchmark_insert_string.cc
+    benchmark_inflate.cc
     benchmark_main.cc
     benchmark_slidehash.cc
     benchmark_uncompress.cc
diff --git a/test/benchmarks/benchmark_inflate.cc b/test/benchmarks/benchmark_inflate.cc
new file mode 100644 (file)
index 0000000..49b94e1
--- /dev/null
@@ -0,0 +1,169 @@
+/* benchmark_inflate.cc -- benchmark inflate() without crc32/adler32
+ * Copyright (C) 2024-2025 Hans Kristian Rosbach
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <benchmark/benchmark.h>
+
+extern "C" {
+#  include "zbuild.h"
+#  include "zutil_p.h"
+#  if defined(ZLIB_COMPAT)
+#    include "zlib.h"
+#  else
+#    include "zlib-ng.h"
+#  endif
+#  include "compressible_data_p.h"
+}
+
+#define MAX_SIZE (1024 * 1024)
+#define NUM_TESTS 6
+
+class inflate: public benchmark::Fixture {
+private:
+    uint8_t *inbuff;
+    uint8_t *outbuff;
+    uint8_t *compressed_buff[NUM_TESTS];
+    z_uintmax_t compressed_sizes[NUM_TESTS];
+    uint32_t sizes[NUM_TESTS] = {1, 64, 1024, 16384, 128*1024, 1024*1024};
+
+public:
+    void SetUp(::benchmark::State& state) {
+        int err;
+        outbuff = (uint8_t *)malloc(MAX_SIZE + 16);
+        if (outbuff == NULL) {
+            state.SkipWithError("malloc failed");
+            return;
+        }
+
+        // Initialize input buffer with highly compressible data, interspersed
+        // with small amounts of random data and 3-byte matches.
+        inbuff = gen_compressible_data(MAX_SIZE);
+        if (inbuff == NULL) {
+            free(outbuff);
+            outbuff = NULL;
+            state.SkipWithError("gen_compressible_data() failed");
+            return;
+        }
+
+        // Initialize Deflate state
+        PREFIX3(stream) strm;
+        strm.zalloc = NULL;
+        strm.zfree = NULL;
+        strm.opaque = NULL;
+        strm.total_in = 0;
+        strm.total_out = 0;
+        strm.next_out = NULL;
+        strm.avail_out = 0;
+
+        err = PREFIX(deflateInit2)(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, -15, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+        if (err != Z_OK) {
+            state.SkipWithError("deflateInit2 did not return Z_OK");
+            return;
+        }
+
+
+        // Compress data into different buffers
+        for (int i = 0; i < NUM_TESTS; ++i) {
+            compressed_buff[i] = (uint8_t *)malloc(sizes[i] + 64);
+            if (compressed_buff[i] == NULL) {
+                state.SkipWithError("malloc failed");
+                return;
+            }
+
+            strm.avail_in = sizes[i];                   // Size of the input buffer
+            strm.next_in = (z_const uint8_t *)inbuff;   // Pointer to the input buffer
+            strm.next_out = compressed_buff[i];         // Pointer to the output buffer
+            strm.avail_out = sizes[i] + 64;             // Maximum size of the output buffer
+
+            err = PREFIX(deflate)(&strm, Z_FINISH);     // Perform compression
+            if (err != Z_STREAM_END ) {
+                state.SkipWithError("deflate did not return Z_STREAM_END");
+                PREFIX(deflateEnd)(&strm);
+                return;
+            }
+
+            compressed_sizes[i] = strm.total_out;       // Total compressed size
+
+            err = PREFIX(deflateReset)(&strm);                // Reset Deflate state
+            if (err != Z_OK) {
+                state.SkipWithError("deflateReset did not return Z_OK");
+                return;
+            }
+        }
+
+        err = PREFIX(deflateEnd)(&strm);                // Clean up the deflate stream
+        if (err != Z_OK) {
+            state.SkipWithError("deflateEnd did not return Z_OK");
+            return;
+        }
+    }
+
+    void Bench(benchmark::State& state) {
+        int err;
+        int index = 0;
+        while (sizes[index] != (uint32_t)state.range(0)) ++index;
+
+        // Initialize the inflate stream
+        PREFIX3(stream) strm;
+        strm.zalloc = NULL;
+        strm.zfree = NULL;
+        strm.opaque = NULL;
+        strm.next_in = NULL;
+        strm.avail_in = 0;
+
+        err = PREFIX(inflateInit2)(&strm, -15);  // Initialize the inflate state, no crc/adler
+        if (err != Z_OK) {
+            state.SkipWithError("inflateInit did not return Z_OK");
+            return;
+        }
+
+        for (auto _ : state) {
+            // Perform reset, avoids benchmarking inflateInit and inflateEnd
+            err = PREFIX(inflateReset)(&strm);
+            if (err != Z_OK) {
+                state.SkipWithError("inflateReset did not return Z_OK");
+                return;
+            }
+
+            strm.avail_in = compressed_sizes[index];    // Size of the input
+            strm.next_in = compressed_buff[index];      // Pointer to the compressed data
+            strm.avail_out = MAX_SIZE;                  // Max size for output
+            strm.next_out = outbuff;                    // Output buffer
+
+            // Perform decompression
+            err = PREFIX(inflate)(&strm, Z_FINISH);
+            if (err != Z_STREAM_END) {
+                state.SkipWithError("inflate did not return Z_STREAM_END");
+                PREFIX(inflateEnd)(&strm);
+                return;
+            }
+        }
+
+        // Finalize the inflation process
+        err = PREFIX(inflateEnd)(&strm);
+        if (err != Z_OK) {
+            state.SkipWithError("inflateEnd did not return Z_OK");
+            return;
+        }
+    }
+
+    void TearDown(const ::benchmark::State&) {
+        free(inbuff);
+        free(outbuff);
+
+        for (int i = 0; i < NUM_TESTS; ++i) {
+            free(compressed_buff[i]);
+        }
+    }
+};
+
+#define BENCHMARK_INFLATE(name) \
+    BENCHMARK_DEFINE_F(inflate, name)(benchmark::State& state) { \
+        Bench(state); \
+    } \
+    BENCHMARK_REGISTER_F(inflate, name)->Arg(1)->Arg(64)->Arg(1024)->Arg(16<<10)->Arg(128<<10)->Arg(1024<<10);
+
+BENCHMARK_INFLATE(inflate_nocrc);
diff --git a/test/benchmarks/compressible_data_p.h b/test/benchmarks/compressible_data_p.h
new file mode 100644 (file)
index 0000000..752a0d6
--- /dev/null
@@ -0,0 +1,37 @@
+/* compressible_data_p.h -- generate compressible data
+ * Copyright (C) 2025 Hans Kristian Rosbach
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#ifndef COMPRESSIBLE_DATA_P_H
+#define COMPRESSIBLE_DATA_P_H
+
+// Alloc and initialize buffer with highly compressible data,
+// interspersed with small amounts of random data and 3-byte matches.
+static uint8_t *gen_compressible_data(int bufsize) {
+    const char teststr1[42] = "Hello hello World broken Test tast mello.";
+    const char teststr2[32] = "llollollollollo He Te me orld";
+    const char teststr3[4] = "bro";
+    int loops = 0;
+
+    uint8_t *buffer = (uint8_t *)malloc(bufsize + 96); // Need extra space for init loop overrun
+    if (buffer == NULL) {
+        return NULL;
+    }
+
+    for (int pos = 0; pos < bufsize; ){
+        pos += sprintf((char *)buffer+pos, "%s", teststr1);
+        buffer[pos++] = (uint8_t)(rand() & 0xFF);
+        // Every so often, add a few other little bits to break the pattern
+        if (loops % 13 == 0) {
+            pos += sprintf((char *)buffer+pos, "%s", teststr3);
+            buffer[pos++] = (uint8_t)(rand() & 0xFF);
+        }
+        if (loops % 300 == 0) { // Only found once or twice per window
+            pos += sprintf((char *)buffer+pos, "%s", teststr2);
+        }
+        loops++;
+    }
+    return buffer;
+}
+#endif