lore-storage: Batch FastCDC chunking to cut per-chunk overhead#61
lore-storage: Batch FastCDC chunking to cut per-chunk overhead#61Vazcore wants to merge 6 commits into
Conversation
write_fragmented called FastCDC::cut once per chunk, shipping the work to the compute pool, awaiting the result, and repeating. For a 1 GiB buffer with ~4 KiB chunks that is ~256k spawn dispatches, oneshot allocations and await yields — overhead that can rival the chunking work itself for small remaining chunks. Compute all FastCDC boundaries in a single compute_pool task by driving the FastCDC Iterator to completion. Fixed-size chunking has trivial per-step math so its boundaries are computed inline. The single-fragment fast path and the storage dispatch loop are unchanged. Verified by a new parity test that compares the batched boundaries against a reference FastCDC iteration on a 256 KiB random buffer, plus edge cases for empty, sub-min-size, all-zero, and non-aligned fixed-size inputs. Signed-off-by: Oleksii Habrusiev <alexgabrusev@gmail.com>
2fe3c48 to
80d041b
Compare
…nt-optimization Signed-off-by: Oleksii Habrusiev <alexgabrusev@gmail.com> # Conflicts: # lore-storage/src/fragment_engine.rs
e560d28 to
f2071c6
Compare
mjansson
left a comment
There was a problem hiding this comment.
The average chunk size is 64KiB, so 1GiB would be 16k tasks - but yes, I agree with the idea.
Some comments, please address them.
Also, please do some micro benchmarks to see if this has any actual measurable impact.
…ation Signed-off-by: Oleksii Habrusiev <alexgabrusev@gmail.com>
2b89c6a to
7b60d66
Compare
@mjansson thanks for review. I created a draft PR to run some benchmarks using "criterion". I updated this PR description to include a raw log and also some summary of improvement.
At 64 KB the overhead of a single boundary is too small to notice. But for real-world writes (1 MB and up) the batch approach is consistently 40-47% faster. |
Signed-off-by: Oleksii Habrusiev <alexgabrusev@gmail.com>
…c allocation in FastCDC chunking Signed-off-by: Oleksii Habrusiev <alexgabrusev@gmail.com>
When you store a big file in Lore, write_fragmented cuts it into chunks using FastCDC and ships each cut point to the compute pool separately, waits for the answer, then does it again. For a 1 GiB buffer with 64 KiB average chunks I think that's around 16k spawn dispatches, oneshot allocations and await yields. The actual chunking work is fast, but all that round-tripping added up to a real cost on medium and large writes I believe.
Why
A single chunking pass on the compute pool is enough - there's no reason to wait per boundary.
How
A single compute_pool task drives the FastCDC Iterator to completion and sends back a list of boundaries through one oneshot. The existing storage dispatch loop then iterates them locally. For fixed-size chunking the boundaries are just step arithmetic so those are computed inline.
The Arc wrapper is gone since we only touch the chunker once now. The single-fragment fast path, the JoinSet of storage tasks, hash-only mode and clone_buffer are all preserved, and the public signature of write_fragmented is unchanged. Behaviorally the output is identical to the old code, same cut points for the same input.
Testing
I added a small reference helper that does the standard FastCDC iteration synchronously and compared its output to the batched path on a 256 KiB random buffer. There's also coverage for empty input, sub-min-size input, an all-zero buffer (which defeats the rolling hash), and a non-aligned fixed-size case. cargo test -p lore-storage goes from 154 to 160 passing, clippy is clean with -D warnings, and the whole workspace still compiles. The fastcdc_batch_matches_reference test is the one that would catch a regression in the FastCDC version or a misconfiguration of the chunk sizes.
Testing using Criterion
Draft PR with criterion: #103
Micro-benchmark results
Comparing the new batched approach against a simulation of the old per-chunk approach.
Same FastCDC config as the real code: min=32KB, expected=64KB, threshold=256KB, Level1.
What's being measured
Results
Why it matters
At 64 KB the overhead of a single boundary is too small to notice. But for real-world
writes (1 MB and up) the batch approach is consistently 40-47% faster. A 1 GB file
with ~16K chunk boundaries saves ~16K spawn dispatches, oneshot allocations and await
yields — replacing them with a single dispatch.
Run locally:
cargo bench -p lore-storage --bench chunkingRaw log: