feat(mutators): add shuffle/unshuffle byte-transposition filter - #1169
Merged
Merged
Conversation
Groups fixed-width elements by byte position, so a following compressor
sees runs of similar bytes instead of the interleaving that defeats
LZ77. This is the classic filter shipped by Blosc, HDF5 and Parquet.
It compresses nothing by itself; it is meant to be chained:
ccat data.bin -m shuffle,zstd
ccat data.sz -m unzstd,unshuffle
On 160KB of float64 samples, where general-purpose compressors are
weak or counterproductive:
zstd xz shuffle,zstd shuffle,xz
random doubles 160019 143208 141909 129828
smooth series 160019 124732 126483 107812
(the input is 160000 bytes: zstd alone grows it)
Element size is the standard X:8 argument, so it also covers float32
and int32 (shuffle:4), RGB pixels (shuffle:3) and so on. Blocks of
64KiB are transposed at a time and both directions derive the same
block size from the element size, so the stream needs no header and
stays streamable. Trailing bytes that do not fill an element pass
through untouched.
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 to closing #850: rather than adding a dependency on a superseded 2007 float codec, this implements the classic byte-shuffle filter that Blosc, HDF5, Parquet and Zarr all ship — in ~90 lines of pure Go, no dependency.
Why it works
An IEEE-754 double keeps its sign and exponent in the high bytes and its noisy mantissa in the low ones. For a series of similar values the high bytes barely change:
The redundancy is real but it lives in columns, while the file is laid out in rows. LZ77-family compressors (gzip, zstd, lz4) only find repeated sequences, and the stable bytes are stranded one per eight — never adjacent. So they find nothing and add framing overhead, which is why zstd grows float64 data.
Shuffling transposes the byte planes, turning those columns into contiguous runs:
Results
160000-byte inputs:
Both beat what fpc achieved in #850 (143137 / 111527), with no new dependency.
Design
-m shuffle,zstdto pack,-m unzstd,unshuffleto unpack.X:8argument — soshuffle:4for float32/int32,shuffle:3for RGB pixels, any fixed-width record with correlated fields.filtercategory, next to the other non-compressing transforms.Verified
Round-trips byte-exactly through files and through a 4-stage stdin pipeline (
-m shuffle,zstd,unzstd,unshuffle). Tests cover empty input, sub-element input, non-multiples of the element size, sizes 1/3/4/8, multi-block data and a partial final block, plus an assertion that shuffle+zstd genuinely beats zstd alone.golangci-lintv2: 0 issues; full suite green; builds on thenohl,fileonlytag set too.🤖 Generated with Claude Code