Conversation
RAD reads the codec per property from its `compression` field, so a file can carry a mix. Today the only codec is `gz` (raw DEFLATE). Third-band SH is what makes a RAD file heavy, and DEFLATE is most of the reason. On an 18.3M-splat SH3 scan the non-SH blobs are 293 MB and the SH is 358 MB on top; zstd takes the SH to 180 MB and leaves the rest alone, so the file goes 651 -> 471 MB. Per splat that is 16.0 bytes of everything-else against 19.6 bytes of SH with DEFLATE or 9.8 with zstd. The 151.6M-splat scene in Spark's own streaming example carries maxSh 0 and no SH at all. How much DEFLATE leaves on the table varies, so the file-level win does too: a second SH3 scene gains only 2.8%, because DEFLATE already reached 36% of raw there. zstd lands within 0.5% of the same size on both. An SH-0 file gains nothing. Decode is the steadier win. Whole files through this branch's own wasm, with an uncompressed transcode as the dequant-only baseline: the 18.3M-splat scene decodes in 10.7 s as gz and 6.5 s as zstd (dequant alone 5.9 s); the small scene 275 ms against 201 ms (164 ms). The codec alone runs about 340 MB/s for gz against 1013 for zstd on the small scene, and roughly 280 against 1500 on the big one. In a native C++ RAD reader the same swap measures zlib 302 MB/s against libzstd 1565 MB/s. It also overlaps with --cluster-sh, which attacks the same bytes: on the small scene clustering alone takes the file 15.44 -> 11.02 MB and zstd then adds 0.5%. This adds `zstd` on both sides: decode through libzstd (the `zstd` crate, which compiles to wasm32 and runs there), and `build-lod --zstd [--zstd-level=N]`, default 9. The `gz` arm is untouched, old files are unaffected, and two round-trip tests cover the frames zstd writes with and without a content size in the header. The cost is the wasm: libzstd adds 244 KB to spark_rs_bg.wasm (1.60 -> 1.85 MB), 228 KB to the built module, and the BUILD needs a clang that can target wasm32 - consumers of the published dist need no toolchain.
Author
|
Addendum: The description undersold
Clustering is the bigger size win, and on a clustered file zstd adds no bytes: what remains compresses the same under both codecs, so such files can stay |
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.
Why
RAD compresses each property blob independently and reads the codec per blob from its
compressionfield. Today the only codec isgz(raw DEFLATE).Third-band SH is what makes a RAD file heavy, and DEFLATE is most of the reason. Summing the blobs of an 18.3M-splat SH3 scan:
Per splat: 16.0 bytes of everything else, against 19.6 bytes of SH with DEFLATE or 9.8 with zstd — SH3 costs 2.2× the scene today, 1.6× with zstd. The 151.6M-splat scene in your streaming example carries
maxSh 0, no SH at all.The size win depends on how much DEFLATE left on the table, not on scene size — chunks are 65,536 splats whatever the scene, and even inside this one the per-chunk win runs from −7% to −31%. A second SH3 scene whose sh3 blob already deflated to 36% of raw (61% above) gained only 2.8%; zstd landed at the same size on both. An SH-0 file gains nothing.
Decode is the steadier win. Whole files through this branch's built
spark_rswasm, with an uncompressed transcode as the dequant-only baseline:Codec alone: scene B inflates at 340 MB/s against zstd's 1,013 (3.0×); scene A about 280 against roughly 1.5 GB/s (the baseline pushes twice the bytes into the decoder, so this flatters both a little, zstd more). Scene B's file is only 2.8% smaller and still decodes 27% faster.
End-to-end, gz file against zstd file in the same Chrome page, scene A, 25 Mbit/s with 20 ms latency, camera inside the scene: 481 → 353 MB fetched and 93.3 → 69.9 s to full detail (−25.1%). At that bandwidth the time is the bytes, so the gain tracks file size; on a fast link or a local file the decode share grows, and with it the gain. Without a browser in the way, our native C++ RAD reader runs zlib at 302 MB/s against libzstd's 1565 MB/s on the same 1335 MB.
Where it does not help
SH-0 files, and clustered ones:
--cluster-sh(#293) attacks the same bytes and often wins bigger — on scene B it takes the file 15.44 → 11.02 MB alone, and zstd then adds 0.5%. Clustering is opt-in, so default files still carry raw SH; on a clustered file this mostly buys decode speed.What
RadChunkPropertyCompression::ZstdbesideGz, decoded per property through libzstd (thezstdcrate), which compiles to wasm32 and runs there.build-lod --zstd [--zstd-level=N], default 9 (faster to encode than gz-6; 19 buys the most offline).gzarm untouched, existing files unaffected, one line added to thebuild-loddocs.Cost
libzstd adds 244 KB to
spark_rs_bg.wasm(1.60 → 1.85 MB) and 228 KB to the built module, and the build needs a clang that can target wasm32 (zstd-sysships a wasm shim;build_rust_wasm.shbuilds unchanged on Ubuntu's clang 18, on Windows it means LLVM on the PATH). Consumers of the publisheddist/need no toolchain. With #418 it could sit behind a feature flag; say the word and I will move it.Pure-Rust decoders were tried first:
ruzstdis correct but ~4× slower in wasm;zrip0.5.1 silently corrupts valid frames on wasm32 while passing on x86_64.Compatibility
A
.radwritten with--zstdis not readable by older Spark versions (unknown compression variant). Existing gz files keep working in old and new builds.Verified
Two round-trip tests for zstd frames with and without a content size; a golden test decoding one source encoded as gz and as zstd to an identical splat hash; and in headless Chromium a zstd
.radand its gz twin streaming throughpaged: trueto the same splat count.Open question
#332 adds zstd for SPZ v4 on the JS side. Since #374 moved decoding into Rust, that path needs a Rust-side zstd too — this one could serve both.