Skip to content

Restore O(1) skip/clear for items without drop glue - #64

Merged
agerasev merged 1 commit into
agerasev:masterfrom
tfenne:skip-trivial-drop-fast-path
Oct 8, 2026
Merged

agerasev merged 1 commit into
agerasev:masterfrom
tfenne:skip-trivial-drop-fast-path

Conversation

@tfenne

@tfenne tfenne commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Since 0.5.2 (#60), Consumer::skip counts removed items one at a time so its guard can publish the right count if a destructor panics. That's needed for items with drop glue, but for items without it (e.g. u8) the counting loop isn't optimised away the way the 0.5.1 loop was, so skip(n) is now O(n), about 0.3 ns per item on an Apple M4 Max. clear(), and dropping a ring (which calls clear()), have the same cost.

This adds a mem::needs_drop::<Self::Item>() fast path that releases min(count, occupied_len()) slots with a single advance_read_index, which is what 0.5.1 effectively compiled to. needs_drop is resolved at compile time for each item type, so the branch has no runtime cost. Items with drop glue still take the panic-safe path from #60, unchanged.

New skip_bytes benchmark (push then skip 32 KiB of u8), cargo +nightly bench --features=bench skip_bytes:

ns/iter
master 7,663
this PR 305

The remaining time is the push_slice copy.

We found this in rawb-io, a read-ahead / write-behind byte IO crate that calls skip on every byte it moves. On 0.5.2 its in-memory throughput dropped from ~80–100 GB/s to ~3.5 GB/s; with this change it's back to the 0.5.1 numbers.

Testing: scripts/test.sh passes. The existing skip tests cover both paths (i8 for the fast path; Rc and the panicking-drop types for the guarded path), and they pass under Miri with and without test_local.

The panic-safe skip counts removed items one at a time so its guard can
publish the right count if a destructor unwinds. For items without drop
glue that loop is not optimised away, making skip, clear and ring drop
O(n). Such items have no destructor to run or unwind, so release their
slots with a single advance_read_index.

Adds a skip_bytes benchmark.
@agerasev
agerasev merged commit 9216850 into agerasev:master Oct 8, 2026
1 check passed
@agerasev

agerasev commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Merged and published.
Thank you!

tfenne added a commit to tfenne/rawb-io that referenced this pull request Oct 8, 2026
Raises the `ringbuf` requirement from `0.5` to `0.5.3` and updates the
lockfile from 0.5.1. Supersedes #9.

In ringbuf 0.5.2, `Consumer::skip` takes time proportional to the number
of items skipped, even for `u8`. Every byte `ReadAhead` and
`WriteBehind` move goes through `skip`, so in-memory throughput fell
from ~80–100 GB/s to ~3.5 GB/s. ringbuf 0.5.3 includes
agerasev/ringbuf#64, which restores the constant-time path. Requiring
0.5.3 ensures downstream builds can't resolve to 0.5.2.

0.5.3 also includes two other upstream changes. I reviewed both against
how rawb-io uses the ring:
- a80f211 removes deferred index updates from the caching and frozen
wrappers. rawb-io uses neither `freeze` nor `pop_iter`.
`advance_write_index` and `advance_read_index` on the caching endpoints
still publish straight to the shared atomic, so the publication order
our protocol relies on is unchanged.
- bb72627 caps capacity at `usize::MAX / 2` and makes the index
arithmetic overflow-safe. Every count rawb-io passes to
`advance_*_index` is at most one capacity, so it meets the new `add_mod`
precondition.

Testing (local, macOS):
- `cargo ci-fmt`, `ci-lint`, `ci-test` (55 passed), `ci-doctest` and
`cargo deny check` all pass.
- `LOOM_MAX_PREEMPTIONS=0 scripts/loom.sh`: all 6 models pass against
the patched 0.5.3 sources, including the new `skip` fast path.
- `cargo ci-soak`: 7/7 pass.
- End-to-end throughput benchmark (separate scratch crate, not
committed) on 0.5.3: ~95–108 GB/s read and ~88–90 GB/s write, back to
0.5.1 levels.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed a performance regression affecting in-memory read-ahead and
write-behind operations. Buffering throughput is no longer significantly
reduced when skipping bytes, restoring faster data handling. Users of
`rawb-io` 0.1.0 can receive the fix by updating their dependencies.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

2 participants