Skip to content

igzip: shorten the x86 level 1 and 2 match search after short matches - #453

Open
ptweezy wants to merge 1 commit into
intel:masterfrom
ptweezy:igzip-x86-short-match-hash
Open

ptweezy wants to merge 1 commit into
intel:masterfrom
ptweezy:igzip-x86-short-match-hash

Conversation

@ptweezy

@ptweezy ptweezy commented Oct 21, 2026 •

Copy link
Copy Markdown

After a match of 4 to 7 bytes, the x86 level 1 and 2 match search cannot hash the next position until it knows the length and has loaded the bytes there. When the match starts at the first of the two positions each iteration checks, those bytes lie within the 8 bytes starting 4 bytes into the match. This PR loads those 8 bytes before the length is known and hashes the next two positions from them, shifted by the length. It also computes a candidate's negated distance as (table[hash] - pos) | ~dist_mask (two instructions instead of four) and reads the input with scalar loads instead of keeping 16 bytes in xmm4.

CPUs and builds

Every x86-64 level 1 and 2 body in igzip_icf_body_h1_gr_bt.asm changes: _01 (SSE4.2), _02 (AVX) and _04 (AVX2 and AVX-512 CPUs). Levels 0 and 3 do not use this code. Output does not change.

Results

master → this PR in MB/s on GitHub-hosted runners, level 1 unless noted: in-process A/B on the Silesia files and 256 slices of the Silesia tar, median over all runs (two per runner) of the best of 11 rounds. In parentheses, the median over runs of the change in median-round throughput (harness; runs 1, 2, 3).

Runner CPU Whole files Whole files, level 2 64 KB slices 4 KB slices
AMD EPYC 7763 (Zen 3), 6 runners 463 → 489 (+5.6%) 447 → 475 (+6.1%) 425 → 447 (+5.3%) 198 → 204 (+3.2%)
AMD EPYC 9V74 (Zen 4), 5 runners 537 → 593 (+10.6%) 510 → 559 (+9.7%) 495 → 548 (+10.8%) 236 → 249 (+5.5%)
AMD EPYC 9V45 (Zen 5), 6 runners 688 → 741 (+7.2%) 656 → 708 (+8.0%) 626 → 669 (+6.6%) 289 → 298 (+3.4%)
Intel Xeon Platinum 8370C (Ice Lake), 3 runners 461 → 502 (+8.9%) 443 → 475 (+7.3%) 433 → 466 (+7.8%) 202 → 210 (+3.9%)
Intel Xeon Platinum 8573C (Emerald Rapids), 1 runner 549 → 598 (+9.0%) 511 → 553 (+7.1%) 501 → 537 (+8.5%) 235 → 241 (+5.1%)
Intel Xeon 6973P-C (Granite Rapids), 3 runners 641 → 692 (+8.1%) 606 → 655 (+7.9%) 593 → 639 (+8.0%) 280 → 290 (+3.6%)

Testing

  • make check passes with the level 1 and 2 dispatch forced to each of _01, _02 and _04, and natively on Windows (mingw-w64, Ryzen 7 9800X3D), where a win64 thunk also finds xmm6-xmm15 and the callee-saved GPRs intact across every body call. The file also assembles for macho64.
  • A differential test against master, with each body forced and the stream at the same address, gives identical return values, avail_in, avail_out and output on every call (stateless and stateful, sync and stateless full flush, all wrappers, hist_bits, dictionaries, guard-page inputs). Silesia sizes at levels 1 and 2 are identical.
  • The body's fixed-size loads reach at most 11 bytes past the current position (master: 15). With 12 bytes of input padding instead of 16, the guard-page test still passes; master faults with 15.

After a match of 4 to 7 bytes, the next position depends on the match
length, so the search runs as a chain: the length, a load at the new
position, its hashes, the hash table entries and the candidates.

When such a match starts at the first of the two positions checked per
iteration, the bytes after it lie in the 8 bytes that start 4 bytes
into the match. Load those before the match is known and hash the two
positions after the match from them, shifted by the match length. A 4
to 7 byte match after a literal still loads the bytes after it once
its length is known.

Compute minus the distance of a candidate as
(table[hash] - pos) | ~dist_mask, two instructions instead of four, and
read the input with scalar loads instead of keeping 16 bytes in xmm4.
The body's fixed-size loads reach at most 11 bytes past the current
position, within the 16 bytes of input padding. The output does not
change.

Level 1 and 2 in-process A/B on the Silesia corpus, Ryzen 7 9800X3D,
against master: whole files +8% to +9%, 4 KB slices +3% to +4%, 16 KB
slices +6%, 64 KB slices +7% to +8%.

Signed-off-by: Parker Loflin <parker@cronstable.dev>
@ptweezy
ptweezy force-pushed the igzip-x86-short-match-hash branch from a07afc7 to ae6e9b1 Compare October 21, 2026 21:27
@ptweezy
ptweezy marked this pull request as draft October 21, 2026 22:01
@ptweezy
ptweezy marked this pull request as ready for review October 21, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant