From: Nathan Moin Vaziri Date: Thu, 18 Jun 2026 03:33:16 +0000 (-0700) Subject: Skip prev writes in deflate_quick hash inserts X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=199ebd277aff8b93f5fec6edc043b6dd77681d60;p=thirdparty%2Fzlib-ng.git Skip prev writes in deflate_quick hash inserts 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. --- diff --git a/deflate.c b/deflate.c index cdc992a31..03fe96250 100644 --- 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)) { diff --git a/deflate.h b/deflate.h index 51a9efee1..f8790d744 100644 --- 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 { diff --git a/deflate_quick.c b/deflate_quick.c index 4bcb652cf..e92b09fcf 100644 --- a/deflate_quick.c +++ b/deflate_quick.c @@ -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) { diff --git a/insert_string.c b/insert_string.c index 75ff2629f..21b323767 100644 --- a/insert_string.c +++ b/insert_string.c @@ -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); +} diff --git a/insert_string_p.h b/insert_string_p.h index 871cd39f0..4de42de84 100644 --- a/insert_string_p.h +++ b/insert_string_p.h @@ -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;