Repository navigation
Share one RandomNumberGenerator and draw random bytes in batches - #703
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
RandomNumberGeneratorpolyfills created and disposed aRandomNumberGeneratorfor every call, andGetItems,GetStringandShuffledid that once per element throughGetInt32(GetString(62 choices, 32)allocated 16.5 KB on net48).Fill: net4x, netstandard2.0, netcoreapp2.0).RandomNumberGeneratorinstances are thread safe, soGetInt32,GetBytesandFilluse one static instance, created on first use.GetInt32reads its four bytes into a thread-static buffer (cleared after use) instead of a newbyte[4]per call. The masked rejection sampling is unchanged.Fill(Span<byte>)goes through a pooled buffer in chunks of up to 4096 bytes instead of a temporary array the size of the destination. The buffer is cleared before it goes back to the pool. (GetBytes(byte[], int, int)is not used: on .NET Framework it allocates an array of its own.)GetItems/GetString/Shuffle(everything below net8) draw their random bytes from a 256 byte stack buffer that is refilled when used up, rather than making one call to the generator per element. Each value is still produced by the same masked rejection sampling over four fresh random bytes, so the distribution is the same and stays unbiased; only the number of calls to the generator changes.GetStringbuilds strings of up to 256 chars on the stack instead of in achar[].GetHexStringis not changed, but gets cheaper on net4x throughFillandGetBytes.Not included:
Random.NextBytes(Span<byte>)(that is in theRandomPR).Tests
GetInt32: uniform over a range that is not a power of two (30,000 draws, each of 3 values within 9,000..11,000), and correct from 8 threads at onceFill: lengths 0, 1, 31, 4095, 4096, 4097 and 100,000 into the middle of a larger array: nothing outside the span is written, every chunk including the tail is filled; and from 8 threads at onceGetItems: uniform for 1, 2, 3, 62, 256 and 300 choices (400 draws per choice, each count within 250..550), lengths around the 64 element buffer boundary, empty destination, empty choices throwGetString: lengths 0, 1, 32, 256, 257 and 5000 use only the choices, and differ between callsShuffle: a permutation for lengths 0, 1, 2, 63, 64, 65 and 2000, and all 6 orders of 3 elements come up evenly over 6,000 shufflesGetBytesandGetHexString(int)at several lengthsThe bounds on the statistical checks are more than 6 standard deviations wide. The new tests pass against the previous implementation and against the BCL on net8+.
Measurements
Stopwatch /
GC.GetAllocatedBytesForCurrentThread, best of 9 rounds after warm-up, Release. The machine was busy, so treat the times as indicative; the allocation figures are exact.net48
GetInt32(100)GetInt32(10, 1000)GetBytes(32)Fill(32B)Fill(64KB)GetItems(62 choices, 32 dest)GetItems(62 choices, 32)GetString(62 choices, 32)Shuffle(32 ints)GetHexString(32)GetHexString(32 dest)net6.0 (
GetInt32,GetBytesandFillare the BCL's there)GetItems(62 choices, 32 dest)GetItems(62 choices, 32)GetString(62 choices, 32)Shuffle(32 ints)