-
Notifications
You must be signed in to change notification settings - Fork 54
Add LZ4HC & LZ4OPT & LZ4MID support #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
PSeitz
wants to merge
50
commits into
main
Choose a base branch
from
hc_pr
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 43 commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
ca4617f
Add LZ4HC, LZ4OPT & LZ4MID compression support
yujincheng08 36be3c0
Rename variables in compress_hc.rs to match compress.rs conventions
PSeitz 41d819c
Extract try_ext_dict_match helper to deduplicate ext_dict matching
PSeitz 63804a9
Flatten nesting in HashTableHCU32 search methods
PSeitz 68b032e
refactor: simplify compress_hc_internal match selection with labeled …
PSeitz 1632b4e
refactor: extract inner fns from compress_mid_internal to methods/fun…
PSeitz 04da7ba
refactor: tighten visibility and remove debug test in compress_hc
PSeitz aeba8ad
refactor
PSeitz c55e28e
clippy
PSeitz c56539a
add comment
PSeitz 94f1afa
cleanup, refactor
PSeitz ba15c51
renames
PSeitz dbde976
refactor
PSeitz e11ea04
align variable naming
PSeitz 449c4b8
refactor
PSeitz a1bee2e
replace loop, add benchmark
PSeitz 6a61ee4
flatten branches
PSeitz f25e109
align naming conventions
PSeitz 3f975c5
align naming conventions
PSeitz b3d2ca5
Rename reference_position to candidate in Match struct
PSeitz 8c92728
add exact test
PSeitz 6188e6c
cleanup bench
PSeitz 4fec1be
Rename hash-chain compressor and bump binggan
PSeitz 37c9799
cleanup hashtable
PSeitz 6d52d3f
Add level 2 to hc benchmark
PSeitz 5326e3f
refactor
PSeitz c63f480
refactor
PSeitz 6c6e75f
refactor structure
PSeitz 6ebcf28
move hc to seperate files
PSeitz bead0b1
Clarify lz4mid 8-byte hash endianness
PSeitz 013652b
Clarify lz4mid match start and end
PSeitz 8964448
Extract lz4mid match finder
PSeitz 295dbcb
add missing inline
PSeitz 9e8c74a
add reuse hashtable bench
PSeitz bf54190
precheck for collisions
PSeitz b9a52b9
remove struct
PSeitz 7edd342
add comment
PSeitz ecefbdc
add block format description
PSeitz c93e821
add USE_DICT in mid.rs
PSeitz 715368a
ignore bench files in rg
PSeitz 7de2d2f
rename mid to two-hashtables
PSeitz 2cd4070
better docs
PSeitz d8d32b4
Refactor HC hash-chain match helpers
PSeitz 5739b5b
Improve LZ4HC compression ratio by removing prehash
PSeitz dcb58ec
Update binggan benchmark dependency
PSeitz 5b52063
attribution
PSeitz d651d72
ignore test files
PSeitz 5562534
remove duplicated code
PSeitz d841efc
remove unnecessary code
PSeitz 2d8b32c
introduce CandidateSource
PSeitz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Ignore benchmark corpus data in ripgrep results. | ||
| # Keep Rust benchmark source files searchable. | ||
| benches/** | ||
| !benches/**/*.rs | ||
|
|
||
| # Ignore downloaded benchmark corpus files, if present. | ||
| benchmarks/bench_files/** |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Code style | ||
|
|
||
| - No abbreviations in variable names (`match_length` not `ml`, `forward_len` not `fwd`) | ||
| - No static methods — use freestanding functions or instance methods | ||
| - No logic blocks inside boolean expressions — extract a function instead | ||
| - Variable names must be self-explanatory without context (`cur` not `off`) | ||
| - Name variables the way you would describe them — if you'd say "can't beat the best match", name it `cant_beat_best`, not `dominated` | ||
| - No meaningless suffixes or qualifiers — every word in a name must carry information (e.g. don't say "local" when there is no non-local counterpart) | ||
| - Align naming with compress.rs conventions (`cur`, `candidate`, `literal_start`, `match_limit`) | ||
| - Extract repeated patterns into named functions | ||
| - Flatten nesting: prefer early returns/continues over deep `if` blocks — use guard clauses instead of `if { value } else { 0 }` patterns |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| # `compress_hc.rs` high-level algorithm overview | ||
|
|
||
| This file contains **three different high-compression strategies** behind the same public API. | ||
|
|
||
| At a high level, all three produce normal LZ4 sequences: | ||
|
|
||
| - a run of **literals** | ||
| - followed by a **match** | ||
| - an `offset` | ||
| - a `match length` | ||
|
|
||
| What changes between the strategies is **how hard they search for matches** and **how they choose between competing matches**. | ||
|
|
||
| ## Strategy selection | ||
|
|
||
| `compress_hc()` first clamps the level and maps it to one of these strategies: | ||
|
|
||
| - **levels 0-2**: `TwoHashTables` | ||
| - **levels 3-9**: `HashChain` | ||
| - **levels 10-12**: `Optimal` | ||
|
|
||
| That mapping is defined by `hc_level_params()`. | ||
|
|
||
| --- | ||
|
|
||
| ## 1. Two-hashtables strategy | ||
|
|
||
| **Used for levels 0-2** | ||
|
|
||
| ### Core idea | ||
|
|
||
| This is the lightweight HC mode. | ||
|
|
||
| It keeps two plain hash tables: | ||
|
|
||
| - one keyed by **4-byte sequences** | ||
| - one keyed by **8-byte sequences** | ||
|
|
||
| Each table stores only the **most recent** position for a hash. | ||
|
|
||
| So unlike the full hash-chain HC mode, it does **not** keep a long linked history of older candidates. | ||
|
|
||
| ### How it works | ||
|
|
||
| At each position: | ||
|
|
||
| 1. Try the **8-byte hash** first | ||
| 2. If that does not produce a usable match, try the **4-byte hash** | ||
| 3. If a match is found: | ||
| - extend it backward if possible | ||
| - extend it forward as far as bytes keep matching | ||
| - emit it immediately | ||
| 4. If no match is found, move forward with a small acceleration rule | ||
|
|
||
| There is also a small amount of local lookahead around the 4-byte path, but the strategy is still mostly greedy. | ||
|
|
||
| --- | ||
|
|
||
| ## 2. Hash-chain HC strategy | ||
|
|
||
| **Used for levels 3-9** | ||
|
|
||
| ### Core idea | ||
|
|
||
| Hash-chain HC stores previous positions for each 4-byte hash: | ||
|
|
||
| - `dictionary[hash]`: newest position with that hash | ||
| - `chain_table[position % chain_table.len()]`: delta to the previous position with that hash | ||
|
|
||
| ```rust | ||
| previous_position = position - chain_table[position % chain_table.len()] | ||
| ``` | ||
|
|
||
| Only positions within the LZ4 maximum match distance are usable. Each search | ||
| checks at most `max_attempts` chain links. | ||
|
|
||
| ### How it works | ||
|
|
||
| At each position: | ||
|
|
||
| 1. Insert the current position into the dictionary and chain table. | ||
| 2. Look up the current 4-byte hash in the dictionary to get the newest candidate. | ||
| 3. Walk backward through the chain, stopping at `max_attempts` or when the candidate is too old. | ||
| 4. For each candidate, check whether it really matches and keep the best match found at this position. | ||
| 5. Before emitting, search again near the end of that match, at `match.end() - 2`, for a better follow-up match. | ||
| 6. If the matches overlap, trim or shorten them so the emitted LZ4 sequences stay valid, then emit. | ||
|
|
||
| For levels 3-9, `max_attempts = 1 << (level - 1)`. | ||
|
|
||
| --- | ||
|
|
||
| ## 3. Optimal parsing strategy | ||
|
|
||
| **Used for levels 10-12** | ||
|
|
||
| ### Core idea | ||
|
|
||
| This mode still uses strong hash-chain-based match finding, but the decision rule changes completely. | ||
|
|
||
| Instead of asking: | ||
|
|
||
| > What is the best match right now? | ||
|
|
||
| it asks: | ||
|
|
||
| > What sequence of literals and matches gives the cheapest encoded output over the next window? | ||
|
|
||
| So this is a small **dynamic-programming parser**. | ||
|
|
||
| ### How it works | ||
|
|
||
| At a position: | ||
|
|
||
| 1. Find a strong initial match | ||
| 2. If that match is obviously good enough, emit it immediately as a fast path | ||
| 3. Otherwise, build an optimal parse window | ||
| 4. For each reachable position in that window, track the best known encoding cost to get there | ||
| 5. Refine those costs by exploring additional matches from intermediate positions | ||
| 6. Reverse the winning path and emit the chosen sequence of literals and matches | ||
|
|
||
| The DP state is stored in the `OptimalState` array. | ||
|
|
||
| ### Cost model | ||
|
|
||
| The parser compares the byte cost of choices using helper functions such as: | ||
|
|
||
| - `literals_price()` | ||
| - `sequence_price()` | ||
|
|
||
| So it is explicitly choosing the path with the best compressed size inside the lookahead window. | ||
|
|
||
| ### Level differences | ||
|
|
||
| Within optimal mode: | ||
|
|
||
| - **level 10**: moderate search | ||
| - **level 11**: deeper search | ||
| - **level 12**: most exhaustive update/search behavior | ||
|
|
||
| ### Decision style | ||
|
|
||
| This mode is: | ||
|
|
||
| - the **slowest** of the three | ||
| - the **best ratio** of the three | ||
| - the only one doing explicit **cost-based path optimization** | ||
|
|
||
| ### Mental model | ||
|
|
||
| > Draft several possible futures, score them by encoded size, and choose the cheapest path. | ||
|
|
||
| --- | ||
|
|
||
| ## Quick comparison | ||
|
|
||
| | Strategy | Levels | Match search | Match choice | Speed | Ratio | | ||
| |---|---:|---|---|---|---| | ||
| | TwoHashTables | 0-2 | Very shallow | Greedy/local | Fastest of the three | Lowest of the three | | ||
| | Hash-chain HC | 3-9 | Deep chain walk | Local + lazy overlap resolution | Middle | Better | | ||
| | Optimal | 10-12 | Deep chain walk | Dynamic programming over a window | Slowest | Best | | ||
|
|
||
| --- | ||
|
|
||
| ## How to read `compress_hc.rs` | ||
|
|
||
| If you want to read the file top-down, this is the main story: | ||
|
|
||
| 1. `compress_hc()` | ||
| - chooses the strategy from the level | ||
| 2. `compress_two_hash_tables_internal()` | ||
| - two-hash-tables, mostly greedy compression | ||
| 3. `compress_hash_chain_internal()` | ||
| - hash-chain search plus lazy local match resolution | ||
| 4. `compress_opt_internal()` | ||
| - optimal parsing with a dynamic-programming window | ||
|
|
||
| That is the main conceptual split in the file. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.