Improve CBOR request marshalling performance: pooled buffer, zero-copy blobs, and reduced validation overhead#4450
Open
muhammad-othman wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Improves CBOR request marshalling performance across generated marshallers and the CBOR extensions/runtime by reducing per-request allocations (pooled request body buffer, avoiding MemoryStream.ToArray() copies), lowering CBOR conformance overhead, and optimizing float encoding.
Changes:
- Add an
initialCapacityconstructor toPooledContentStreamto rent a right-sized pooled buffer when the encoded size is known up front. - Update CBOR request/structure marshaller templates to (a) encode into
PooledContentStream(non-.NET Framework) and (b) writeMemoryStreamblobs without allocating a copy. - Switch
CborWriterPooltoCborConformanceMode.Lax, add aWriteByteString(MemoryStream)overload + tests, and optimize float32 encoding for .NET 8+.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/src/Core/Amazon.Runtime/Internal/PooledContentStream.cs | Adds a pre-sized pooled-stream constructor to support right-sized buffer renting. |
| generator/ServiceClientGeneratorLib/Generators/Marshallers/CborStructureMarshaller.tt | Updates generated CBOR structure marshalling for MemoryStream blobs to avoid .ToArray() allocations. |
| generator/ServiceClientGeneratorLib/Generators/Marshallers/CborStructureMarshaller.cs | Regenerated T4 output reflecting MemoryStream blob marshalling changes. |
| generator/ServiceClientGeneratorLib/Generators/Marshallers/CborRequestMarshaller.tt | Updates generated CBOR request marshalling to encode into PooledContentStream on non-.NET Framework TFMs. |
| generator/ServiceClientGeneratorLib/Generators/Marshallers/CborRequestMarshaller.cs | Regenerated T4 output reflecting pooled-buffer encoding changes. |
| generator/.DevConfigs/917b930a-b60b-4e06-909f-586826f59759.json | DevConfig entries for Core + Extensions.CborProtocol patch releases and changelog messages. |
| extensions/test/CborProtocol.Tests/WriteByteStringTests.cs | Adds test coverage for the WriteByteString(MemoryStream) zero-copy/fallback behavior. |
| extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborWriterPool.cs | Switches the pool to create writers using CborConformanceMode.Lax. |
| extensions/src/AWSSDK.Extensions.CborProtocol/CborWriterExtensions.cs | Adds WriteByteString(MemoryStream) overload and allocation-free float32 encoding on .NET 8+. |
…ectly into a pooled buffer.
e8b31b5 to
d348a90
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.
Description
Encode directly into a pooled buffer. Generated CBOR marshallers previously called
request.Content = writer.Encode(), which allocates a freshbyte[]per request. They now encode into aPooledContentStream(backed by a pooledArrayPoolBufferWriter<byte>) and setrequest.ContentStream, matching the JSON (#4409) and XML pattern. The downstream pipeline already understandsPooledContentStream, and the buffer is returned to the pool after the request completes.Pre-size the pooled buffer. The buffer is rented at
writer.BytesWritten(the exact encoded size, known up front) so it's allocated at the right size in one shot, avoiding a default-size rent followed by a resize-and-return.Zero-copy binary blobs. Added
CborWriterExtensions.WriteByteString(MemoryStream), which writes the stream's underlying buffer directly viaMemoryStream.TryGetBufferinstead of allocating a copy with.ToArray(), falling back to.ToArray()when the buffer isn't publicly visible. This is the main win for large binary payloads.Lower CBOR validation overhead.
CborWriterPoolnow creates writers withCborConformanceMode.Laxinstead ofCanonical. AWS CBOR services do not require canonical (sorted) map-key ordering, so Lax is safe and skips the per-request key-sorting work. Verified against AWS services and the RPC v2 CBOR protocol tests.Allocation-free float encoding.
WriteOptimizedNumber(float)now encodes the float32 form directly into astackallocbuffer viaBinaryPrimitives.WriteSingleBigEndianon .NET 8+, removing the per-callbyte[5]allocation.Allocation improvements
The reason: the old path allocated a fresh
byte[]fromwriter.Encode()whose size scaled with the request body. The new path encodes into a pooledArrayPoolBufferWriter<byte>that is rented and returned per request, so it doesn't count as a per-request allocation, which leave only the fixed marshalling overhead (request object, headers, the stream wrapper), which is independent of body size.byte[]: BinaryData_L dropped ~46% (513.72 KB → 274.78 KB) and no longer triggers Gen2 collections from a per-request large-object allocation. (Residual allocation there is the blob copy itself, only taken when the source stream's buffer isn't directly accessible.)Mean latency improvements
(Full before/after tables are in the testing section.)
Motivation and Context
DOTNET-8587Testing
WriteByteStringTestscovering the zero-copy path, the non-exposable fallback, empty streams, position-independence, and large payloads (output is byte-identical to.ToArray()).Before
After
Dry-runs
Breaking Changes Assessment
Screenshots (if appropriate)
Types of changes
Checklist
License