Repository navigation
zstd: decode at full speed into exact-size destinations - #1246
Conversation
When the frame content size is known, useSafeDecodeSync required the output buffer to have compressedBlockOverAlloc bytes of slack beyond the whole rest of the frame (maxSyncLen). A destination sized exactly to the frame therefore ran the bounds-exact copies for every block, although the extended copies only need the slack past the block being decoded, which produces at most min(maxSyncLen, maxBlockSize) bytes. The assembly still checks each sequence against cap-compressedBlockOverAlloc, so a frame that lies about its size fails as before. Test in zstd/seqdec_test.go. Co-Authored-By: Claude Opus 5.5 <[email protected]>
executeSimple grew s.out whenever the block plus compressedBlockOverAlloc did not fit. With a destination sized exactly to the frame that happens on the last block, and the growth copied the whole decoded frame into a new buffer about twice its size, so DecodeAll also returned a different backing array than the caller supplied. Grow only when the block itself does not fit, and use the bounds-exact copies when the output lacks the slack. The two-pass path that calls executeSimple from DecodeAll runs on arm64 for windows of 1 MiB and up. Test in zstd/decoder_test.go. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe Zstandard decoder adjusts output growth and copy-variant selection based on decoded block size and available buffer slack. Tests cover copy selection and exact-capacity destination reuse. ChangesZstandard decode buffer handling
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~12 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The change avoids unnecessary output reallocation for exact-size destinations and falls back to safe copies when slack is lacking. No merge-blocking risk was identified. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The decoder’s copy-safety decisions change for exact-size output buffers. The reviewed paths retain bounds checks and input limits, and no new security issue was established. Coverage of malformed and multi-frame exact-capacity cases remains limited. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
DecodeAllinto a destination whose capacity is exactly the decoded size ran much slower than the same call with 16 bytes to spare. This is the natural buffer for a caller that knows the decompressed size, for example from the frame header or its own metadata.(1) zstd: bound the decodeSync slack check by the block, not the frame (7ef7a21). When the frame content size is known,
useSafeDecodeSyncasked forcompressedBlockOverAllocbytes of slack beyond the whole rest of the frame (maxSyncLen). With an exact-size destination that is never true, so every block ran the bounds-exact copies; on Neoverse N1 that path was 60% of decode time. The extended copies only need the slack past the block being decoded, which produces at mostmin(maxSyncLen, maxBlockSize)bytes. The assembly still checks every sequence againstcap-compressedBlockOverAllocon that path, so a frame that lies about its size fails as before. Test:TestUseSafeDecodeSyncFrameBoundinzstd/seqdec_test.go.The same change fixes multi-frame input decoded in one
DecodeAllcall.maxSyncLenstarts atFrameContentSize + len(dst), so for every frame after the first it overstated what was left by all the output already written. The old whole-frame check then sent later frames to the bounds-exact copies even when the buffer had plenty of room.(2) zstd: don't reallocate the output for missing slack in executeSimple (1720e5a).
executeSimplegrews.outwhenever the block pluscompressedBlockOverAllocdid not fit. With an exact-size destination that happens on the frame's last block: the growth copied the entire decoded frame into a new buffer about twice its size, andDecodeAllreturned that copy instead of the caller's buffer. It now grows only when the block itself does not fit, and uses the bounds-exact copies when only the slack is missing.DecodeAllreaches this through the two-pass path, which runs on arm64 for windows of 1 MiB and up. Test:TestDecodeAllExactCapacityinzstd/decoder_test.go, which forces the two-pass path so it also runs on amd64. A panic planted in the new branch confirmed the test reaches it.Both tests fail on master and pass here.
go test ./zstdpasses in the default,noasmandnounsafebuilds on amd64 and arm64.Performance
Data: 64 MiB slices of
apache.log,github-june-2days-2019.json,gob-streamandnyc-taxi-data-10M.csvfrom https://klauspost.com/files/compress/, in two shapes:SpeedDefaultandSpeedBetterCompression;All CRC off.
BenchmarkCorpus'sDecodeAllpreallocates exactly the decoded size. Each run is 10 interleaved rounds pinned to one core, compared withbenchstat.This PR vs master, B/s geomean over the 16 files. No regressed cells (0 of 64).
For comparison on N1, giving master's destination 16 spare bytes recovers +10.4% on the single frames (the fix matches all but the last block, which still needs the exact copies), but nothing on the multi-frame files.
Callers that already leave slack are unaffected, because both changes make the same choices for them. That includes
DecodeAllwith a nil or too-smalldst, which allocatesFrameContentSize+16itself. Measured with per-frameDecodeAllinto a reused buffer on the same data, withPCALIGN $64in both builds:The only added work is one compare per block, so I believe that spread is the placement of the Go code, which
PCALIGNdoesn't pin, but I haven't isolated it.🤖 Generated with Claude Code
Summary by CodeRabbit