Repository navigation
THRIFT-6431: Amortize Ruby MemoryBuffer compaction and release exhausted storage - #4020
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The rolling-backlog tests need assertions that detect regression to eager compaction.
1 open issue
What changed in this PR
Improves Ruby MemoryBufferTransport memory usage by amortizing compaction and releasing exhausted storage.
Changes:
- Defers compaction until consumed bytes reach both 4 KiB and the unread backlog size.
- Copies unread suffixes into independent storage in native and pure Ruby implementations.
- Adds regression tests and updates exhausted-buffer diagnostic expectations.
| File | Description |
|---|---|
| lib/rb/spec/base_transport_spec.rb | Tests exhaustion, rolling reads, and data preservation. |
| lib/rb/lib/thrift/transport/memory_buffer_transport.rb | Adds deferred compaction with a cached boundary. |
| lib/rb/ext/memory_buffer.c | Implements matching native compaction and storage release. |
Review effort: Balanced
…ted storage Client: rb Compact unread storage only after consuming at least 4 KiB and at least as many bytes as remain. Detach copied suffixes and replace exhausted storage, including after partial reads. Cache the next compaction boundary in pure Ruby to keep ordinary byte reads inexpensive. Cover rolling reads, input ownership, resets and exhaustion, and update diagnostic expectations for released storage. Co-Authored-By: OpenAI Codex (gpt-6.1-sol) <codex@openai.com>
kpumuk
force-pushed
the
rb-memory-buffer-compaction
branch
from
October 16, 2026 21:57
e8a10c3 to
fb50fbf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

MemoryBufferTransport replaces its unread backlog after every 4 KiB consumed, making small rolling reads unnecessarily expensive. Exhausting a small shared suffix can also keep a much larger String allocation attached to an empty transport.
The native and pure Ruby implementations now compact only after consuming at least 4 KiB and at least as many bytes as remain, copy the unread suffix into independent storage, and replace exhausted storage. Pure Ruby caches the next compaction boundary to keep ordinary byte reads inexpensive. Returned data, caller-owned Strings and partial-read error behavior are preserved. Exhausted buffers now produce empty diagnostic dumps, reflecting the released storage.
Benchmarks
Compared master
7941f5426deb5eca8d8be0cdb0aed4312914d71awith this change on CRuby 4.0.7, Linux aarch64, using the pure Ruby implementation. Process order alternated between variants.Memory measurements are medians of three fresh processes per variant and mode. The rolling workload maintains a 2 MiB backlog across 32 reads/writes of 4 KiB. The exhaustion workload consumes 16 MiB followed by its final 1 KiB and keeps only the transport alive.
String footprint uses
ObjectSpace.count_objects_size[:T_STRING]; it is not total allocated memory. The rolling delta excludes initial input setup and includes output verification. Post-GC retention includes small probe overhead. RSS is the whole process's Linux VmHWM, not a delta; releasing String references does not guarantee that the allocator returns memory to the OS immediately.Elapsed-time measurements use three fresh processes per variant, each with five ordinary-GC samples. The table shows medians of process medians:
These are bounded synthetic workloads on one Ruby version and architecture. Small timing differences should not be treated as general throughput guarantees. No benchmark overlapped this change's builds or tests; unrelated host contention was not controlled.
Benchmark command and source
Save the following as
benchmark_memory_buffer.rb. Fromlib/rb, extract the master implementation and run each command three times, alternating their order:Memory benchmark command and source
Save the following as
benchmark_memory_buffer_memory.rb. Fromlib/rb, use the master source extracted above and run these commands three times, reversing variant order on the second trial:runtimemeasures with ordinary GC;allocdisables GC during the rolling loop;retainedmeasures exhaustion after GC.[skip ci]anywhere in the commit message to free up build resources.