Skip to content

perf: borrow input in ByteStorage write path instead of copying it#48

Merged
27Bslash6 merged 1 commit into
mainfrom
perf/bytestorage-borrow-input
Jun 16, 2026
Merged

perf: borrow input in ByteStorage write path instead of copying it#48
27Bslash6 merged 1 commit into
mainfrom
perf/bytestorage-borrow-input

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem (cachekit-core half of #45)

StorageEnvelope::new took its payload as Vec<u8> by value, but only ever:

  • lz4_flex::compress(&data) — borrows
  • xxh3_64(&data) — borrows

The Vec is never retained. So ByteStorage::store(&[u8]) was forced to data.to_vec() its borrowed input before calling new — a full-payload allocation + memcpy on every write, up to MAX_UNCOMPRESSED_SIZE (512 MB), for nothing.

Fix

Change StorageEnvelope::new to take &[u8]. store() passes its slice straight through. One fewer copy per write, scaling with payload size — directly trims the large-object write-path peak RSS that motivated the cachekit-py 9.4× write-RSS work.

// before
let envelope = StorageEnvelope::new(data.to_vec(), format)?;
// after
let envelope = StorageEnvelope::new(data, format)?;

API note (greenfield, no back-compat)

StorageEnvelope::new is pub but functions as an internal constructor — the load-bearing public API, ByteStorage::store(&[u8]), is unchanged. Across the workspace, no production code calls StorageEnvelope::new directly; only tests and fuzz targets do (updated here). cachekit-py's python_bindings.rs goes through store. Downstream fuzz targets that call new directly update when they bump core.

This is a perf: change → it will cut a release; consumers pick up the faster core on their next bump.

Scope

Only the primary input copy. The secondary rmp_serde::to_vec(&envelope) re-copy of compressed_data (also noted in #45) needs manual envelope framing and is left out deliberately (KISS).

The GIL-release half of #45 (py.allow_threads) lives in cachekit-py's PyO3 bindings, not here.

Verification

  • cargo fmt --check ✅ · cargo clippy --all-features -- -D warnings
  • cargo test --all-features ✅ (all suites pass; behavior unchanged)
  • cargo +nightly check on the fuzz crate ✅ (edited targets compile)
  • Directional micro-bench (byte_storage/roundtrip, before vs after): 5 of 6 sizes faster. Numbers are noisy (short 2s window; roundtrip includes the unaffected retrieve/decompress; bench ceiling is 64 KB, well below where copy-elimination dominates) so I'm not citing precise %s — the real, provable win is the eliminated ≤512 MB copy on the write path. Extending the bench to MB-scale store-only is a worthwhile follow-up.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Updated storage envelope construction to accept borrowed byte buffers instead of requiring owned copies, reducing memory allocations and improving performance during compression/checksum processing.
  • Tests
    • Updated unit test coverage and fuzz harnesses to use the new borrowed-data flow while preserving existing integrity and failure-condition checks.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3fd51de9-5bce-431f-ab01-73bedebeec82

📥 Commits

Reviewing files that changed from the base of the PR and between 739cb50 and 832e773.

📒 Files selected for processing (4)
  • fuzz/fuzz_targets/byte_storage_checksum_collision.rs
  • fuzz/fuzz_targets/byte_storage_format_injection.rs
  • fuzz/fuzz_targets/integration_layered_security.rs
  • src/byte_storage.rs

Walkthrough

StorageEnvelope::new is changed to accept a borrowed &[u8] instead of an owned Vec<u8>. The compression and checksum internals are updated to operate on the borrowed slice, ByteStorage::store drops its to_vec() allocation, and all three fuzz targets are updated to pass references.

Changes

StorageEnvelope::new borrow refactor

Layer / File(s) Summary
StorageEnvelope::new signature, internals, and store call site
src/byte_storage.rs
Signature changed from data: Vec<u8> to data: &[u8]; LZ4 compression and xxHash3-64 calls updated to use the borrowed slice; ByteStorage::store passes the input slice directly, removing the data.to_vec() allocation.
Unit test updates
src/byte_storage.rs
Compression, checksum-validation, and size-limit unit tests updated to pass &data or a borrowed slice into StorageEnvelope::new.
Fuzz target call site updates
fuzz/fuzz_targets/byte_storage_checksum_collision.rs, fuzz/fuzz_targets/byte_storage_format_injection.rs, fuzz/fuzz_targets/integration_layered_security.rs
All three fuzz targets updated to pass borrowed references (&test_case.data, &test_data, &pattern_data, plaintext) instead of cloned or owned Vec<u8> values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarises the main change: optimising the write path by borrowing input instead of copying it, which aligns with the core objective of eliminating unnecessary memory allocation in ByteStorage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/bytestorage-borrow-input

Comment @coderabbitai help to get the list of available commands and usage tips.

StorageEnvelope::new took its payload as Vec<u8> by value, but only ever
compresses (lz4_flex::compress(&data)) and hashes (xxh3_64(&data)) it — both
borrow. The Vec was never retained, so ByteStorage::store was forced to
data.to_vec() its &[u8] input first: a full-payload allocation + memcpy (up to
MAX_UNCOMPRESSED_SIZE = 512 MB) on every write, for nothing.

Change StorageEnvelope::new to take &[u8]; store() passes its slice straight
through. One fewer copy per write, scaling with payload size — directly trims
the large-object write-path peak RSS.

Addresses the cachekit-core half of #45. The py.allow_threads / GIL-release
half lives in the cachekit-py PyO3 bindings and is tracked there.

API note: StorageEnvelope::new is pub but functions as an internal
constructor; the load-bearing API ByteStorage::store(&[u8]) is unchanged.
Greenfield, no back-compat. Internal tests + 3 fuzz targets updated to pass
slices.
@27Bslash6 27Bslash6 force-pushed the perf/bytestorage-borrow-input branch from 739cb50 to 832e773 Compare June 15, 2026 22:09
@27Bslash6 27Bslash6 merged commit 7f5ebc1 into main Jun 16, 2026
30 checks passed
@27Bslash6 27Bslash6 deleted the perf/bytestorage-borrow-input branch June 16, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant