Batched random for the CSPRNG (and System.Random)
@bartonjs is already working on this.
Since Jan 16, 2025.
Assessment
This issue has not been assessed yet.
Description
Background and motivation
When generating many small random numbers, it is more efficient to generate them in one bulk operation. The most obvious example would be 8 coin-flips can be generated from just one random byte.
Several algorithms exist for extracting multiple bounded integers from a pull of an RNG, without sacrificing a loss of entropy. For example, if one needs two integers in the range [0, 6), they independently compose to a single integer in the range [0, 12). These algorithms can significantly speed up throughput on systems with a slow random number generator.
API Proposal
namespace System.Security.Cryptography
{
// Batched random
public partial class RandomNumberGenerator
{
// Fill a homogeneous set of bounded integers.
// Target: RNG.GetItems, dice roll simulations.
public static void FillInt32(int toExclusive, Span<int> destination);
public static void FillInt32(int fromInclusive, int toExclusive, Span<int> destination);
public static void FillUInt32(uint toExclusive, Span<uint> destination);
// Fill a heterogeneous set of bounded integers.
// Target: Fisher-Yates Shuffle
public static void FillUInt32(ReadOnlySpan<uint> toExclusives, Span<uint> destination);
// Fully squaring off the feature:
// T: Int32, UInt32, Int64, UInt64
// public static void FillT(T toExclusive, Span<T> destination);
// public static void FillT(T fromInclusive, T toExclusive, Span<T> destination);
// public static void FillT(ReadOnlySpan<T> toExclusives, Span<T> destination);
// public static void FillT(ReadOnlySpan<T> fromInclusives, ReadOnlySpan<T> toExclusives, Span<T> destination);
public static void FillUInt32(uint fromInclusive, uint toExclusive, Span<uint> destination);
public static void FillInt64(long toExclusive, Span<long> destination);
public static void FillInt64(long fromInclusive, long toExclusive, Span<long> destination);
public static void FillUInt64(ulong toExclusive, Span<ulong> destination);
public static void FillUInt64(ulong fromInclusive, ulong toExclusive, Span<ulong> destination);
public static void FillUInt32(ReadOnlySpan<uint> fromInclusives, ReadOnlySpan<uint> toExclusives, Span<uint> destination);
public static void FillInt32(ReadOnlySpan<int> toExclusives, Span<int> destination);
public static void FillInt32(ReadOnlySpan<int> fromInclusives, ReadOnlySpan<int> toExclusives, Span<uint> destination);
public static void FillInt64(ReadOnlySpan<long> toExclusives, Span<long> destination);
public static void FillInt64(ReadOnlySpan<long> fromInclusives, ReadOnlySpan<long> toExclusives, Span<long> destination);
public static void FillUInt64(ReadOnlySpan<ulong> toExclusives, Span<ulong> destination);
public static void FillUInt64(ReadOnlySpan<ulong> fromInclusives, ReadOnlySpan<ulong> toExclusives, Span<ulong> destination);
}
// Helper routines that would have made investigating this space easier
public partial class RandomNumberGenerator
{
public static ulong GetUInt64();
public static ulong GetUInt64(uint toExclusive);
// The rest of these are "squaring the circle"
public static int GetInt32();
public static uint GetUInt32();
public static uint GetUInt32(uint toExclusive);
public static uint GetUInt32(uint fromInclusive, uint toExclusive);
public static long GetInt64();
public static long GetInt64(long toExclusive);
public static long GetInt64(long fromInclusive, long toExclusive);
public static ulong GetUInt64(uint fromInclusive, uint toExclusive);
}
}
namespace System
{
public partial class Random
{
// Only one exception case: toExclusive == 0
public virtual void NextBatchUInt32(uint toExclusive, Span<uint> destination);
// Only one exception case: toExclusives.Any(x => x == 0);
public virtual void NextBatchUInt32(ReadOnlySpan<uint> toExclusives, Span<uint> destination);
// Non-virtual, implemented in terms of NextBatchUInt32
public void NextBatch(int toExclusive, Span<int> destination);
public void NextBatch(int fromInclusive, int toExclusive, Span<int> destination);
// squaring off
public void NextBatchUInt32(uint fromInclusive, uint toExclusive, Span<uint> destination);
public void NextBatchUInt32(ReadOnlySpan<uint> fromInclusives, ReadOnlySpan<uint> toExclusives, Span<uint> destination);
public void NextBatchInt32(ReadOnlySpan<int> toExclusives, Span<int> destination);
public void NextBatchInt32(ReadOnlySpan<int> fromInclusives, ReadOnlySpan<int> toExclusives, Span<int> destination);
public void NextBatchInt64(long toExclusive, Span<long> destination);
public void NextBatchInt64(long fromInclusive, long toExclusive, Span<long> destination);
public void NextBatchInt64(ReadOnlySpan<long> toExclusives, Span<long> destination);
public void NextBatchInt64(ReadOnlySpan<long> fromInclusives, ReadOnlySpan<long> toExclusives, Span<long> destination);
public virtual void NextBatchUInt64(ulong toExclusive, Span<ulong> destination);
public void NextBatchUInt64(ulong fromInclusive, ulong toExclusive, Span<ulong> destination);
public virtual void NextBatchUInt64(ReadOnlySpan<ulong> toExclusives, Span<ulong> destination);
public void NextBatchUInt64(ReadOnlySpan<ulong> fromInclusives, ReadOnlySpan<ulong> toExclusives, Span<ulong> destination);
}
}
API Usage
int[] dice = new int[5];
RandomNumberGenerator.FillInt32(6, dice);
for (int i = 0; i < 6; i++)
{
if (dice.All(die => die == i))
{
return "Yahtzee!";
}
}
Alternative Designs
- FillInt32 and friends can be
intreturning instead ofvoid, to indicate how many items they processed in a single batch.- For the virtuals on System.Random we might want to encapsulate that with the template method pattern to ensure they don't return negative, or zero.
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/runtime
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
area-System.Reflection blocking-clean-ci-optional Known Build Error os-mac-os-x untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
area-CodeGen-coreclr untriaged
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
agentic-workflows untriaged
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
area-VM-meta-mono untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
Create parent directories only after the containment check in InstallHelper.TryExtractToDirectory Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
PowerShell/PSResourceGet#2056 ·