zlib/DEFLATE 慢速匹配路径

在有限窗口中查找重复串,并比较相邻匹配

zlib 用哈希桶和按窗口位置组织的前驱链缩小候选范围,并以搜索预算和一次前瞻在速度与压缩率之间取舍。

767c4c947存储与数据放置核验 2026-10-12
相关概念

工程问题#

DEFLATE 要把重复字节串转换为距离与长度。逐字扫描整个历史窗口昂贵,只看一个候选又可能遗漏更好的匹配;还要决定立即使用当前匹配,还是把一个字节留为字面量后使用下一处更长匹配。

实际实现#

INSERT_STRING 把当前位置加入哈希桶,prev 数组按窗口掩码保存同桶的前驱。longest_match 沿链检查窗口内候选,受到 max_chain_length、nice_match 和最大匹配长度限制。deflate_slow 暂存前一位置的匹配;若当前位置没有更好结果,才输出前一匹配对应的距离和长度。

处理流程#

  1. 对短前缀计算滚动哈希,并把当前位置链接到桶头
  2. 沿同桶历史位置查找,排除窗口外候选并限制搜索预算
  3. 记录匹配长度,与相邻位置的候选比较
  4. 输出前一匹配或继续保留字面量,将结果交给后续编码阶段

源码片段#

固定版本 · 原文摘录
deflate.c767c4c947852e143f582c85f14cf573411df1b35 · L159–L164
查看原文件
159#else160#define INSERT_STRING(s, str, match_head) \161   (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \162    match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \163    s->head[s->ins_h] = (Pos)(str))164#endif
INSERT_STRING

head 保存桶头,prev 按环形窗口位置链接旧桶头;这里只产生候选。

deflate.c767c4c947852e143f582c85f14cf573411df1b35 · L1390–L1403
查看原文件
1390local uInt longest_match(deflate_state *s, IPos cur_match) {1391    unsigned chain_length = s->max_chain_length;/* max hash chain length */1392    Bytef *scan = s->window + s->strstart;      /* current string */1393    Bytef *match;                               /* matched string */1394    int len;                                    /* length of current match */1395    int best_len = (int)s->prev_length;         /* best match length so far */1396    int nice_match = s->nice_match;             /* stop if match long enough */1397    IPos limit = s->strstart > (IPos)MAX_DIST(s) ?1398        s->strstart - (IPos)MAX_DIST(s) : NIL;1399    /* Stop when cur_match becomes <= limit. To simplify the code,1400     * we prevent matches with the string of window index 0.1401     */1402    Posf *prev = s->prev;1403    uInt wmask = s->w_mask;
longest_match

记录最大链探测次数、当前最好长度与历史窗口下界。

deflate.c767c4c947852e143f582c85f14cf573411df1b35 · L1515–L1530
查看原文件
1515        if (len > best_len) {1516            s->match_start = cur_match;1517            best_len = len;1518            if (len >= nice_match) break;1519#ifdef UNALIGNED_OK1520            scan_end = *(ushf*)(scan + best_len - 1);1521#else1522            scan_end1  = scan[best_len - 1];1523            scan_end   = scan[best_len];1524#endif1525        }1526    } while ((cur_match = prev[cur_match & wmask]) > limit1527             && --chain_length != 0);15281529    if ((uInt)best_len <= s->lookahead) return (uInt)best_len;1530    return s->lookahead;
longest_match

更新最好匹配,到足够长度或探测预算耗尽便停止;返回值限制在 lookahead 内。

deflate.c767c4c947852e143f582c85f14cf573411df1b35 · L1976–L1995
查看原文件
1976        /* Insert the string window[strstart .. strstart + 2] in the1977         * dictionary, and set hash_head to the head of the hash chain:1978         */1979        hash_head = NIL;1980        if (s->lookahead >= MIN_MATCH) {1981            INSERT_STRING(s, s->strstart, hash_head);1982        }19831984        /* Find the longest match, discarding those <= prev_length.1985         */1986        s->prev_length = s->match_length, s->prev_match = s->match_start;1987        s->match_length = MIN_MATCH-1;19881989        if (hash_head != NIL && s->prev_length < s->max_lazy_match &&1990            s->strstart - hash_head <= MAX_DIST(s)) {1991            /* To simplify the code, we prevent matches with the string1992             * of window index 0 (in particular we have to avoid a match1993             * of the string with itself at the start of the input file).1994             */1995            s->match_length = longest_match (s, hash_head);
deflate_slow

插入当前位置,保存前一候选,并在条件允许时继续搜索。

deflate.c767c4c947852e143f582c85f14cf573411df1b35 · L2011–L2021
查看原文件
2011        /* If there was a match at the previous step and the current2012         * match is not better, output the previous match:2013         */2014        if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) {2015            uInt max_insert = s->strstart + s->lookahead - MIN_MATCH;2016            /* Do not insert strings in hash table beyond this. */20172018            check_match(s, s->strstart - 1, s->prev_match, (int)s->prev_length);20192020            _tr_tally_dist(s, s->strstart - 1 - s->prev_match,2021                           s->prev_length - MIN_MATCH, bflush);
deflate_slow

当前匹配没有更好时输出前一匹配的距离与长度,体现一次前瞻的选择。

摘录分别保留调用入口、核心机制及边界;完整上下文见各段源码链接。

收益与取舍#

工程分析

带来的收益

  • 哈希链避免每次遍历窗口中所有起点,prev 数组随固定窗口复用
  • 有限搜索和 lazy matching 可在不做全局解析优化的情况下改善局部选择

付出的代价

  • 哈希相等只表示候选,仍需真实字节比较;碰撞和重复数据会影响搜索成本
  • 达到链预算或足够好的长度便停止,不保证找到窗口内最长匹配或全局最短编码
  • 窗口、哈希表和链数组占用内存;此处不包含最终 Huffman 编码及块类型选择

可比较的选择

单候选哈希表减少链遍历,但更容易覆盖仍有价值的历史候选。

更完整的匹配搜索与解析优化可能改善压缩率,也会增加搜索时间、状态和内存开销。

版本与适用范围#

固定到提交 767c4c947852。分析非 FASTEST 构建中的 deflate_slow 和通用 longest_match;不同压缩级别、strategy、构建选项可选用其他路径。搜索复杂度取决于探测数和字节比较量,不能仅凭哈希表断言整体为常数时间。

参考来源#

源码、官方文档与原始论文
01
zlib · INSERT_STRING

deflate.c · INSERT_STRING

源码767c4c947852e143f582c85f14cf573411df1b35

相关实现与比较#

4 个相关案例
QEMU·TCG 翻译块v9.2.0

QEMU TCG 用 QHT 保存 TranslationBlock 索引,帮助复用与当前客体执行状态一致的翻译结果。

附源码片段 · 缓存与复用
9 个匹配案例