Repository navigation
Size the buffers of the Base64 OperationStatus overloads by the data - #697
Merged
Merged
Conversation
This was referenced Oct 12, 2026
This was referenced Oct 14, 2026
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.
The
OperationStatusoverloads of theBase64polyfill go through a temporary UTF-8 buffer:EncodeToChars(source, destination, out, out)allocatednew byte[destination.Length], so encoding 32 bytes into a 64K char buffer allocated 65 KBDecodeFromChars(source, destination, out, out)allocatednew byte[source.Length]Both now size the buffer by the data, use the stack up to 256 bytes and
ArrayPool<byte>.Sharedabove that, so neither allocates.For the encode the buffer is
Math.Min(destination.Length, encoded length of source):Base64.EncodeToUtf8never writes more than the encoded length of the source, so capping the buffer at that gives the same status, consumed and written counts as a buffer the size of the destination.The loops live in separate helpers from the
stackalloc: the JIT does not tier up a method that has both, which measured several times slower in a cold process.Separate from #688 (which changes the other overloads); the two touch different members.
Tests
Convertfor every length from 0 to 400 (all remainders, stack and pooled buffers), with a destination one element larger than needed (sentinel checked) and one 5000 elements largerThe new tests pass against the previous implementation and against the real net11
Base64. On net4xSystem.MemoryreportsNeedMoreDatafor a not-final block with nothing left over, so that one status is checked asDoneorNeedMoreData.Measurements
Stopwatch /
GC.GetAllocatedBytesForCurrentThread, best of 9 rounds after warm-up, Release.net10.0
EncodeToChars(32B, 64K dest, out, out)EncodeToChars(1KB, 64K dest, out, out)DecodeFromChars(32B, dest, out, out)DecodeFromChars(1KB, dest, out, out)net48
EncodeToChars(32B, 64K dest, out, out)EncodeToChars(1KB, 64K dest, out, out)DecodeFromChars(32B, dest, out, out)DecodeFromChars(1KB, dest, out, out)(The machine was busy, so differences of a few tens of ns are noise.)