perf(fsst): close the vortex-jni gap on encode and decode hot paths#300
Open
dfa1 wants to merge 1 commit into
Open
perf(fsst): close the vortex-jni gap on encode and decode hot paths#300dfa1 wants to merge 1 commit into
dfa1 wants to merge 1 commit into
Conversation
Compressor: single unaligned 8-byte word loads on the fast path (byte-by-byte loadWord kept for the <8-byte tail), escape literal taken from the loaded word. LossyPerfectHashTable: packed-int lookup over a two-longs-per-slot table (the paper's C layout) — one cache line per lookup instead of three parallel-array reads; empty slots self-answer no-match with no occupancy flag or sentinel. ShortCodeTable/Matcher: pre-packed no-match slots, one array read per fallback. FsstEncodingEncoder: all rows compress into one shared scratch (was two heap allocations plus an extra copy per row), one-pass wire-code remap. FsstEncodingDecoder: prefix-sum output offsets from the lengths child and 256-row batched decompression (per-row switch/modulo removed; whole-chunk single call measured 1.8x slower via OSR-compiled mega-loop), plus new malformed-input guards (empty children, invalid offsets, row-count and decoded-length overflow, decoded-vs-claimed mismatch) with negative tests. JavaVsJniFsstBenchmark -f 3: encode 1.848 -> 3.059 +- 0.430 ops/s (jni 2.854, parity), decode 27.137 -> 32.924 +- 0.824 ops/s (jni 28.353, 1.16x faster). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Follow-up hot-path pass over the #287 FSST rewrite, iterated against
JavaVsJniFsstBenchmarkwith per-round profiling (-prof stack:lines=10).Results (
-f 3, same machine/run for both sides)vortex-jnijavaFsstEncodejavaFsstDecodeChanges
fsst module (encode hot loop):
Compressor.compress(both overloads): single unaligned LE 8-byte word load while ≥8 input bytes remain (byteArrayViewVarHandle/LE_LONG); byte-by-byteloadWordretained for the tail only. The fast path drops the over-long-match guard (a ≤8-byte match cannot crossendthere) and takes the escape literal from the already-loaded word.LossyPerfectHashTable: lookup returns a packedcode << 8 | lengthint (0 = miss) instead of allocating aHashMatchrecord; table stored as two adjacent longs per slot (symbol +ignoredBits<<16|code<<8|length, the paper's C layout) so a lookup touches one cache line instead of three parallel arrays; empty slots are all-zero and self-answer no-match.ShortCodeTable/Matcher: pre-packed no-match sentinel makes the fallback one branch-free array read;longestMatchevaluates both tables and selects (cmov-friendly).codeOf(noMatch) == NO_CODEsemantics preserved.writer adapter:
FsstEncodingEncoder: all rows compress back-to-back into one shared scratch buffer (was a fresh scratch + exact-size copy per row — 2M allocations per 1M-row chunk write), wire-code remap in a single pass, offsets fromrowEnds.reader adapter:
FsstEncodingDecoder: output offsets built as per-ptype prefix sums over the uncompressed-lengths child, and the code stream decoded in 256-row batches (per-row ptypeswitchand twoi % capbroadcast modulos removed from the 1M-row loops). Batching is deliberate: a single whole-chunkdecompresscall measured ~1.8x slower per byte (OSR-compiled mega-loop) — documented onROWS_PER_DECODE_BATCH.FsstEncodingDecoderTest(10 tests).Verification
./mvnw verifygreen (all modules, integration tests included) — Rust interop (JavaWritesRustReads,RustWritesJavaReads) confirms wire output unchanged.vortex-reviewerpass: APPROVE; its should-fix (negative tests for the new guards) and nit (>2GB decoded-length guard) are included here.-f 3table; TODO FSST follow-ups refreshed (stale 1.6x-gap phrasing).🤖 Generated with Claude Code