Skip to content

feat(fsst): branch-free matching (ShortCodeTable + LossyPerfectHashTable + Matcher)#291

Merged
dfa1 merged 1 commit into
mainfrom
feat/fsst-branch-free-matching
Jul 20, 2026
Merged

feat(fsst): branch-free matching (ShortCodeTable + LossyPerfectHashTable + Matcher)#291
dfa1 merged 1 commit into
mainfrom
feat/fsst-branch-free-matching

Conversation

@dfa1

@dfa1 dfa1 commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

PR 2 of the #287 FSST rewrite sequence. Implements the paper's Algorithm 4 ("lossy perfect hashing") as an O(1) branch-free replacement for the current encoder's up-to-8-sequential-hash-probe matching loop — the single biggest perf gap identified against the paper/Rust reference.

  • ShortCodeTable — 65536-entry direct array resolving 0/1/2-byte matches in one read.
  • LossyPerfectHashTable — 2048 slots (matching the spiraldb/fsst reference's L1D-cache reasoning, our actual vortex-jni comparison target, not the paper's 4096) resolving 3-8 byte matches via one hash lookup + one masked compare, no probing.
  • Matcher — composes both, no loop over candidate lengths.

Collision-ordering invariant (load-bearing): the hash table's single-forward-pass first-writer-wins insertion requires the caller to pass symbols in descending-gain order. Tested with both a real collision (higher gain wins the slot) and an adversarial hash-collision-but-byte-mismatch case (masked compare, not the hash alone, gates a hit).

Purely additive — writer/reader untouched. Wired into training in PR 3.

Test plan

  • ./mvnw test -pl fsst -am — 28 tests, 0 failures
  • ./mvnw verify -pl fsst -am — checkstyle passes
  • ./mvnw javadoc:javadoc -pl fsst — zero output
  • ./mvnw verify -DskipTests at root — full 16-module reactor SUCCESS

🤖 Generated with Claude Code

…ble + Matcher)

Implements the FSST paper's Algorithm 4 ("lossy perfect hashing", §5.1) as the
matching core for the extracted fsst module (issue #287, PR 2 of the plan in
goofy-roaming-moler.md).

WHY: the current writer's FsstEncodingEncoder.SymbolTable.longestMatch probes
symbol lengths 8 down to 1 in a per-position loop — up to eight sequential
open-addressing hash lookups per input byte. That variable-length loop is
exactly the non-uniform hot-loop body CLAUDE.md forbids (it blocks C2
auto-vectorization). This replaces it with an O(1) branch-free structure:

- ShortCodeTable: a 65536-entry array direct-indexed on the first two input
  bytes, resolving 0/1/2-byte matches in one array read.
- LossyPerfectHashTable: 2048 slots (matching the spiraldb/fsst Rust reference's
  L1D-cache-line-split reasoning and the vortex-jni benchmark target, not the
  paper's literal 4096), keyed on the first three bytes, resolving 3-8 byte
  matches with one hash lookup plus one masked 8-byte compare — no probing.
- Matcher: composes the two branch-free, no loop over candidate lengths.

Collision-ordering invariant (load-bearing): the hash table inserts in a single
forward pass with first-writer-wins. The caller must pass symbols in
descending-gain order, so on a 3-byte-prefix hash collision the higher-gain
symbol (seen first) keeps the slot and the lower-gain one is rejected, never
overwritten — the paper's rule that the more valuable symbol wins a lossy
collision. These classes do not re-sort their input. Tests cover a real
collision (higher gain wins, lower gain correctly misses) and an adversarial
hash-collision-but-byte-mismatch case proving the masked compare, not the hash
alone, gates a hit.

Purely additive: writer/reader untouched; these are wired into training in PR 3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dfa1
dfa1 merged commit 8d778ba into main Jul 20, 2026
6 checks passed
@dfa1 dfa1 mentioned this pull request Jul 20, 2026
@dfa1
dfa1 deleted the feat/fsst-branch-free-matching branch July 20, 2026 20:58
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