]> git.ipfire.org Git - thirdparty/zlib-ng.git/commitdiff
Skip prev writes in deflate_quick hash inserts
authorNathan Moin Vaziri <nathan@nathanm.com>
Thu, 18 Jun 2026 03:33:16 +0000 (20:33 -0700)
committerHans Kristian Rosbach <hk-github@circlestorm.org>
Thu, 16 Jul 2026 13:01:32 +0000 (15:01 +0200)
deflate_quick only looks at the head of each hash chain and never walks
prev, so writing the prev links is wasted work. Add head-only insert
helpers and use them in deflate_quick and the level-1 fill_window
catch-up inserts.

The leftover stale prev is safe if the level is later raised mid-stream,
since longest_match never records a match with cur_match >= strstart and
verifies every match by comparison.

deflate.c
deflate.h
deflate_quick.c
insert_string.c
insert_string_p.h

index cdc992a3137484c348764b131b2f21c707cecaa7..03fe962509b6f6269e2db4d34a9635bf447e140c 100644 (file)
--- a/deflate.c
+++ b/deflate.c
@@ -135,6 +135,15 @@ static const config configuration_table[10] = {
  * meaning.
  */
 
+/* Level 1 uses deflate_quick, which only reads the hash chain head, so it can
+ * skip prev maintenance. When quick is compiled out level 1 falls back to
+ * deflate_fast, which walks the chains like every other level. */
+#ifdef NO_QUICK_STRATEGY
+#  define HAVE_QUICK_STRATEGY 0
+#else
+#  define HAVE_QUICK_STRATEGY 1
+#endif
+
 /* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */
 #define RANK(f) (((f) * 2) - ((f) > 4 ? 9 : 0))
 
@@ -623,9 +632,10 @@ int32_t Z_EXPORT PREFIX(deflateParams)(PREFIX3(stream) *strm, int32_t level, int
     int hashless = level == 0 || strategy == Z_HUFFMAN_ONLY || strategy == Z_RLE;
     int was_hashless = s->level == 0 || s->strategy == Z_HUFFMAN_ONLY || s->strategy == Z_RLE;
 
-    /* Stale if the hash usage flipped (to/from huffman/rle/stored) or the hash
-     * function changed at level 9. */
-    int stale_chain = (hashless != was_hashless) || (level >= 9) != (s->level >= 9);
+    /* Stale if the hash usage flipped (to/from huffman/rle/stored), the hash
+     * function changed at level 9, or quick at level 1 left prev unmaintained. */
+    int stale_chain = (hashless != was_hashless) || (level >= 9) != (s->level >= 9) ||
+                      (HAVE_QUICK_STRATEGY && s->level == 1 && level != 1);
 
     /* Rebuild the hash chains when fill_window is called. */
     if (stale_chain && !hashless) {
@@ -1200,6 +1210,8 @@ void Z_INTERNAL PREFIX(fill_window)(deflate_state *s) {
 
     if (level >= 9)
         insert_batch = insert_roll_batch;
+    else if (HAVE_QUICK_STRATEGY && level == 1)
+        insert_batch = insert_knuth_batch_head;
     else
         insert_batch = insert_knuth_batch;
 
@@ -1249,7 +1261,10 @@ void Z_INTERNAL PREFIX(fill_window)(deflate_state *s) {
             if (UNLIKELY(level >= 9)) {
                 s->ins_h = update_hash_roll(window[str], window[str+1]);
             } else if (str >= 1) {
-                insert_knuth(s, window, str + 2 - STD_MIN_MATCH);
+                if (HAVE_QUICK_STRATEGY && level == 1)
+                    insert_knuth_head(s, window, str + 2 - STD_MIN_MATCH);
+                else
+                    insert_knuth(s, window, str + 2 - STD_MIN_MATCH);
             }
             unsigned int count = s->insert;
             if (UNLIKELY(s->lookahead == 1)) {
index 51a9efee109737143e2b830ea59d2a527c1e2428..f8790d744ef12f24519bf594d72f81b0c71b9fbc 100644 (file)
--- a/deflate.h
+++ b/deflate.h
@@ -143,9 +143,10 @@ typedef uint16_t Pos;
 /* Type definitions for hash callbacks */
 typedef struct internal_state deflate_state;
 
-typedef void (* insert_batch_func) (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
-void         insert_knuth_batch    (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
-void         insert_roll_batch     (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
+typedef void (* insert_batch_func)   (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
+void         insert_knuth_batch      (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
+void         insert_roll_batch       (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
+void         insert_knuth_batch_head (deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count);
 
 /* Struct for memory allocation handling */
 typedef struct deflate_allocs_s {
index 4bcb652cfc75d98a5040b3be0c780c9f272e291d..e92b09fcf6682588887b16b4f8d595b4d63b93c6 100644 (file)
@@ -95,7 +95,7 @@ Z_FORCEINLINE static block_state deflate_quick_impl(deflate_state *s, int flush,
         uint32_t str_val = Z_U32_FROM_LE(zng_memread_4(window + strstart));
 
         if (LIKELY(lookahead >= WANT_MIN_MATCH)) {
-            uint32_t hash_head = insert_knuth_val(s, strstart, str_val);
+            uint32_t hash_head = insert_knuth_val_head(s, strstart, str_val);
             int64_t dist = (int64_t)strstart - hash_head;
 
             if (dist <= MAX_DIST(s) && dist > 0) {
index 75ff2629f5540fb618a985dde7e9a0393afe7ad6..21b323767ef5551e1c7f15dda93584281ae58c21 100644 (file)
@@ -16,3 +16,7 @@ Z_INTERNAL void insert_knuth_batch(deflate_state *const s, unsigned char *window
 Z_INTERNAL void insert_roll_batch(deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count) {
     insert_roll_batch_static(s, window, str, count);
 }
+
+Z_INTERNAL void insert_knuth_batch_head(deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count) {
+    insert_knuth_batch_head_static(s, window, str, count);
+}
index 871cd39f069da11073c7367144637c38127b3ee1..4de42de84cde73708283c00e7a7cd74457158a97 100644 (file)
@@ -46,6 +46,21 @@ Z_FORCEINLINE static uint32_t insert_knuth_val(deflate_state *const s, uint32_t
     return head;
 }
 
+/* ===========================================================================
+ * Insert string str using a pre-read value, returning the previous head of the
+ * hash chain. The prev link is left untouched since deflate_quick only inspects
+ * the chain head and never walks the chain.
+ */
+Z_FORCEINLINE static uint32_t insert_knuth_val_head(deflate_state *const s, uint32_t str, uint32_t val) {
+    uint32_t h, head;
+
+    UPDATE_HASH_KNUTH(h, val);
+
+    head = s->head[h];
+    s->head[h] = (Pos)str;
+    return head;
+}
+
 /* ===========================================================================
  * Insert string str in the dictionary and set match_head to the previous head
  * of the hash chain (the most recent string with same hash key). Return
@@ -66,6 +81,23 @@ Z_FORCEINLINE static uint32_t insert_knuth(deflate_state *const s, unsigned char
     return head;
 }
 
+/* ===========================================================================
+ * Insert string str read from the window, returning the previous head of the
+ * hash chain. Like insert_knuth but leaves the prev link untouched for
+ * deflate_quick, which only inspects the chain head.
+ */
+Z_FORCEINLINE static uint32_t insert_knuth_head(deflate_state *const s, unsigned char *window, uint32_t str) {
+    uint8_t *strstart = window + str;
+    uint32_t val, h, head;
+
+    val = Z_U32_FROM_LE(zng_memread_4(strstart));
+    UPDATE_HASH_KNUTH(h, val);
+
+    head = s->head[h];
+    s->head[h] = (Pos)str;
+    return head;
+}
+
 Z_FORCEINLINE static uint32_t insert_roll(deflate_state *const s, unsigned char *window, uint32_t str) {
     uint8_t *strstart = window + str + (STD_MIN_MATCH-1);
     uint32_t h, head;
@@ -113,6 +145,25 @@ Z_FORCEINLINE static void insert_knuth_batch_static(deflate_state *const s, unsi
     }
 }
 
+/* ===========================================================================
+ * Insert count strings read from the window, leaving the prev links untouched.
+ * Used by fill_window during deflate_quick, which only inspects the chain head.
+ */
+Z_FORCEINLINE static void insert_knuth_batch_head_static(deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count) {
+    uint8_t *strstart = window + str;
+    uint8_t *strend = strstart + count;
+    Pos *headp = s->head;
+
+    for (uint32_t idx = str; strstart < strend; idx++, strstart++) {
+        uint32_t val, h;
+
+        val = Z_U32_FROM_LE(zng_memread_4(strstart));
+        UPDATE_HASH_KNUTH(h, val);
+
+        headp[h] = (Pos)idx;
+    }
+}
+
 Z_FORCEINLINE static void insert_roll_batch_static(deflate_state *const s, unsigned char *window, uint32_t str, uint32_t count) {
     uint8_t *strstart = window + str + (STD_MIN_MATCH-1);
     uint8_t *strend = strstart + count;