Repository navigation
Conversation
Reuse the input buffer, append chunk and trailer framing directly, and avoid temporary substrings when draining the encoded buffer. Cover binary payloads, partial reads, EOF, and rewind behavior.
0026fde to
3db5d32
Compare
|
I ran this branch against the released A differential fuzz of 9,000 cases gives byte-identical output, and Streaming 100 MiB through it with 16 KiB reads into a reused buffer, as Net::HTTP does, median of 5:
It also complements #3465, which removes the same kind of per-chunk copies when the checksum goes in a header. |
|
Two small follow-ups, in case they help the review. Happy to drop either. Breaking-change surface. As far as I can tell, the only observable change is that the wrapped body now receives The last allocation per chunk. The ~6,400 Strings left for 100 MiB are one per 16 KiB chunk: @base_chunk_size = @chunk_size - @overhead_bytes
+ @full_chunk_header = "#{@base_chunk_size.to_s(HEX_BASE)}\r\n".freeze
@chunk = ''.b- @encoded_buffer << @chunk.bytesize.to_s(HEX_BASE) << "\r\n" << @chunk << "\r\n"
+ @encoded_buffer << chunk_header << @chunk << "\r\n" def chunk_header
return @full_chunk_header if @chunk.bytesize == @base_chunk_size
"#{@chunk.bytesize.to_s(HEX_BASE)}\r\n"
endOn a standalone copy of the class, streaming 100 MiB with 16 KiB reads into a reused buffer (Ruby 4.0.9, arm64 macOS) gives:
The gain is allocations only, not time. A 2,000-case fuzz across random payloads, chunk sizes and read lengths produced byte-identical output. #3465 and this PR. Both touch |
|
Thanks @kpumuk for the allocation improvements and @czj for the additional testing and suggestions! I've added some backwards compatible changes for V3. For V4, I think we can require the IO interface to support @kpumuk, could you rerun your benchmarks against the updated branch to confirm that the compatibility changes retain the allocation and performance improvements? @czj - caching the chunk header looks like a worthwhile optimization! Could you open a separate PR for it? I’ll make a few minor changes to your current PR (#3465), then ask the team to review both PRs in conjunction. |
|
Re-ran the 100 MiB benchmark on Ruby 4.0.6, CRC32, 16 KiB chunks:
Memory and String allocations were measured with GC disabled. RSS, streaming time, and GC cycles are medians of 15 fresh processes per case, with execution order rotated. |
|
The manual Build SDK dry run passed via validation PR #3488: successful run. 🤖 This comment was drafted with AI assistance for @jterapin. ✨ |
ashishdhingra
left a comment
There was a problem hiding this comment.
Verified fixes and specs locally.
Streaming request bodies with checksum trailers creates temporary Strings for input chunks, interpolated framing, and slices of the encoded buffer. For a 100 MiB upload, these accumulate roughly 301 MiB of String memory when GC is disabled.
This change reduces allocations in
AwsChunkedTrailerDigestIOthrough three optimizations:@chunkwith''.band reuse it through@io.read(@base_chunk_size, @chunk).@encoded_buffer, avoiding intermediate interpolated Strings.slice!. Partial reads usebytesliceand retain the remaining suffix; they can still copy data.Benchmark
Measured with a 100 MiB binary file, CRC32, 16 KiB encoding chunks, and Ruby 4.0.6 on arm64 macOS. Each row adds one optimization to the previous row. These measurements were captured during development, before squashing the commits.
ObjectSpace; not total process memory or cumulative allocator traffic.Peak RSS, streaming time, and GC cycles are medians of five fresh processes with normal GC. Memory and allocations come from a separate GC-disabled run. Results vary with Ruby version, platform, system load, and benchmark overhead.
Reproduce
Save the script below as
/tmp/checksum_benchmark.rband run it from the repository root:The script benchmarks only the checked-out implementation. Run it on the base and feature branches to compare them. It uses Ruby’s standard library, local SDK code, and
/usr/bin/timeon macOS or Linux.It prints all five metrics and verifies identical encoded lengths and SHA-256 hashes across its six runs.
Standalone Ruby benchmark
Generated with AI assistance and reviewed by @kpumuk
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.