Skip to content

Commit

Permalink
Fix Random.GetItems observably breaking change in produced sequence (#…
Browse files Browse the repository at this point in the history
…108018)

Developers, often in tests, rely on seeded Random instances producing the same sequence of values on every use. We made a change in .NET 9, though, that changed the sequence GetItems produces, due to employing a different algorithm. This fixes that special-case to only be used when the developer couldn't rely on the results being deterministic, namely when using either `new Random()` or `Random.Shared`. If a seed is provided or if a custom derived implementation is used, it falls back to the old behavior.

Co-authored-by: Stephen Toub <stoub@microsoft.com>
  • Loading branch information
github-actions[bot] and stephentoub committed Sep 19, 2024
1 parent 25e7a89 commit fe77f43
Showing 1 changed file with 6 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/libraries/System.Private.CoreLib/src/System/Random.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,16 @@ public void GetItems<T>(ReadOnlySpan<T> choices, Span<T> destination)

// The most expensive part of this operation is the call to get random data. We can
// do so potentially many fewer times if:
// - the instance was constructed as `new Random()` or is `Random.Shared`, such that it's not seeded nor is it
// a custom derived type. We don't want to observably change the deterministically-produced sequence from previous releases.
// - the number of choices is <= 256. This let's us get a single byte per choice.
// - the number of choices is a power of two. This let's us use a byte and simply mask off
// unnecessary bits cheaply rather than needing to use rejection sampling.
// In such a case, we can grab a bunch of random bytes in one call.
if (BitOperations.IsPow2(choices.Length) && choices.Length <= 256)
ImplBase impl = _impl;
if ((impl is null || impl.GetType() == typeof(XoshiroImpl)) &&
BitOperations.IsPow2(choices.Length) &&
choices.Length <= 256)
{
Span<byte> randomBytes = stackalloc byte[512]; // arbitrary size, a balance between stack consumed and number of random calls required
while (!destination.IsEmpty)
Expand Down

0 comments on commit fe77f43

Please sign in to comment.