perf(channel): reduce send coroutine frame storage - #1051
Conversation
There was a problem hiding this comment.
Pull request overview
Reduces channel<T>::send coroutine-frame storage by letting internal waiters borrow frame-owned payloads while preserving public awaiter ownership.
Changes:
- Introduces private borrowed send-waiter infrastructure.
- Adds move-only lifecycle tests and a frame/performance benchmark.
- Updates API, performance, and release documentation.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
include/elio/sync/channel.hpp |
Implements borrowed internal send waiters. |
tests/unit/test_channel_success.cpp |
Adds ownership, move, and race tests. |
examples/bench_channel_send_frame.cpp |
Benchmarks frame size and send performance. |
examples/channel_send_frame_bench_factory.hpp |
Declares benchmark payloads and factories. |
examples/channel_send_frame_bench_factory.cpp |
Implements non-inlined frame factories. |
examples/CMakeLists.txt |
Builds the new benchmark. |
wiki/API-Reference.md |
Documents send payload ownership. |
wiki/Performance-Tuning.md |
Documents optimization and benchmarking. |
CHANGELOG.md |
Records the frame-size improvement. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@copilot Please review the updated pull request only. |
The updated PR is correct. Here is a summary of the review: Active-token fix (55bd422): The Ownership and lifetime:
Double-unregister safety: Claim-before-move ordering: Close drain: No issues found in the updated code. The PR looks good to merge. |
Description
Reduce the coroutine-frame footprint of
channel<T>::send(T)andsend(T, cancel_token)by removing duplicate payload storage.The public send coroutines already own their by-value
Tparameter for the lifetime of the frame. Their internal send waiter now borrows that parameter through a private queue core instead of moving it into a secondTsubobject. Public directly constructed send awaiters remain owning wrappers and retain their existing inheritance, lifetime, result, cancellation, and exception behavior.For a 256-byte inline payload, the measured send-frame request fell by 29.8% without a token and 46.7% with a token. For a 1024-byte payload, it fell by 42.9% and 60.3%, respectively. Paired Release measurements found no significant regression in non-token ready sends and measured improvements in active-token ready sends and all forced-handoff cases.
Type of Change
Related Issues
Closes #1047
Related to #762
Changes Made
Core Changes
send_waiter_corethat owns the existing queue node, wake state, and arbitration flags while referring to a payload owned elsewhere.send_awaitableandcancellable_send_awaitableas owning wrappers, including the existing public inheritance relationship and directly constructed awaiter behavior.API Changes (if applicable)
No public signature, result, cancellation, or exception-specification changes.
channel<T>::send_awaitableremains an owning public awaiter and retains its publicintrusive_list_node<send_awaitable>base for source compatibility. The new borrowed core is private and is used only by channel-owned coroutine wrappers.Migration Guide (if breaking change)
Not applicable.
Testing
Unit Tests
Integration Tests
Sanitizer Testing
Test Results
All builds were out of source with explicit
--parallel 2. The dedicated benchmark also builds cleanly in Release with the project's warnings-as-errors configuration.Checklist
Code Quality
Documentation
Testing
Compatibility
CHANGELOG.md(if applicable)Performance (if applicable)
Screenshots / Diagrams
Not applicable.
Additional Notes
The frame benchmark was run as 20 pinned, serial, interleaved baseline/candidate pairs. Both variants used the same final benchmark source and compiler flags; only the Elio include root differed.
Frame allocation request results were deterministic across all samples:
The 8-byte no-token payload is padding-bound and remains 336 B. Each factory operation still reports two allocations: the coroutine frame plus the existing task execution/cancellation control block. This change reduces allocated bytes rather than allocation count.
Paired candidate/baseline geometric ratios with 95% bootstrap confidence intervals:
The benchmark's target-local replacement allocation functions are deliberately non-inlined. This prevents GCC from diagnosing the recorder's intentional
malloc/freeimplementation as a mismatched new/delete pair under-Werror; the recorder is isolated from the existingbench_channelexecutable.Reviewer Guidance
Areas requiring special attention:
Questions for reviewers:
Thank you for contributing to Elio! 🎉