rmandvikar.Random2
3.1.0-alpha0
Prefix Reserved
dotnet add package rmandvikar.Random2 --version 3.1.0-alpha0
NuGet\Install-Package rmandvikar.Random2 -Version 3.1.0-alpha0
<PackageReference Include="rmandvikar.Random2" Version="3.1.0-alpha0" />
paket add rmandvikar.Random2 --version 3.1.0-alpha0
#r "nuget: rmandvikar.Random2, 3.1.0-alpha0"
// Install rmandvikar.Random2 as a Cake Addin #addin nuget:?package=rmandvikar.Random2&version=3.1.0-alpha0&prerelease // Install rmandvikar.Random2 as a Cake Tool #tool nuget:?package=rmandvikar.Random2&version=3.1.0-alpha0&prerelease
Random
Few fast thread-safe C# Random implementations.
Background
See: https://csharpindepth.com/articles/Random
TLDR, Random in C# has 2 problems that developers have to code around:
- Random is not thread-safe. In some cases when the Random instance is accessed by multiple threads, it could yield numbers that will always be 0 and then that instance becomes useless. This is for net framework, net core, and net (
net5.0
). - Random instances new'ed up in close time intervals will result in same numbers. This is because Random is deterministic where instances with same seed will give same numbers. The default Random ctor internally uses
Environment.TickCount
as seed which doesn't change for 10-16ms on net framework. This is fixed on net core, and net (net5.0
).
Random API
Random doesn't inherit from an interface so the implementations inherit from Random versus a static class. This makes it easier to swap in whichever implementation that suits the need. RandomFactory
gives out a singleton instance of the implementations as a single Random instance per app domain is the recommendation. The ctors are not public and a specific seed cannot be used.
LockRandom
simply makes pass-through calls to the base instance's methods with a lock, so using a specific seed technically is possible but not done.
ThreadStaticRandom
and ThreadLocalRandom
use similar approaches where a global RNG is used to seed the threads' Random instances. A specific seed is meaningless and the base instance is unused. In previous versions, the global RNG implementations were not exactly the same so as to keep them as the original authors intended, but using a cryptographic RNG for the global seed is better.
To address the shortcomings, a static member with thread-safe impl as Random.Shared
was added in net6.0
. It uses ThreadStatic
underneath (see pr).
Usage
Local variable
// ok to capture in a local var
var random = RandomFactory.GetThreadStaticRandom();
var random = RandomFactory.GetThreadLocalRandom();
var random = RandomFactory.GetLockRandom();
Static class member
Note that it's ok to capture the ThreadStaticRandom
instance too in a static class member, as every impl is a wrapper class.
// ok to capture in a static member
private static readonly Random random = RandomFactory.GetThreadStaticRandom();
private static readonly Random random = RandomFactory.GetThreadLocalRandom();
private static readonly Random random = RandomFactory.GetLockRandom();
Tests
- Showcase_Issues: Showcases different behaviors and problems with Random approaches.
- Verify_Correctness: Showcases issue 1. with Random and how the other approaches don't have it. Particularly,
Verify_Correctness_Random
is the test that showcases the thread-safety issue (issue 1. above) once in a while on net framework and even net core, net (net5.0
). Yikes! - Verify_Perf: Perf 10M iterations of
Next()
call in parallel for different approaches. - Verify_Distribution: Sample 100 iterations to manually check distribution (for patterns, repeats, etc).
Perf
Verify_Perf
for 10M Next()
calls.
Test (net5.0) | Time (ms) |
---|---|
Verify_Perf_NewInstance | 2673 |
Verify_Perf_Random (BUG!) | 237 |
Verify_Perf_NewGuidAsSeed | 2758 |
Verify_Perf_LockRandom | 638 |
Verify_Perf_ThreadLocalRandom | 80 |
Verify_Perf_ThreadStaticRandom | 55 |
Verify_Perf_SharedRandom | - |
Test (net6.0) | Time (ms) |
---|---|
Verify_Perf_NewInstance | 1234 |
Verify_Perf_Random (BUG!) | 208 |
Verify_Perf_NewGuidAsSeed | 2557 |
Verify_Perf_LockRandom | 618 |
Verify_Perf_ThreadLocalRandom | 75 |
Verify_Perf_ThreadStaticRandom | 55 |
Verify_Perf_SharedRandom | 50 |
Recommendations
- Use
LockRandom
if you want a balance of speed and to guard yourself against any issues, patterns, etc. - Use
Random.Shared
directly if onnet6.0
+. It perfs similarly toThreadStaticRandom
impl.RandomFactory.GetSharedRandom()
is added to simply provide a migration path away from this nuget. - Use
ThreadStaticRandom
,ThreadLocalRandom
if you want speed but are ok with the possibility of issues discovered in future (patterns, etc). They are the fastest of the bunch. - Implementations with new Random instances on every call, and using
Guid.NewGuid().GetHashCode()
as seed are the slowest (issue 2. on net framework only).Guid.NewGuid().GetHashCode()
could come with its own problems as it depends on Guid'sGetHashCode()
implementation. - Implementation with global Random instance is riskiest (issue 1. above), so definitely avoid that.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net6.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
3.1.0-alpha0 | 85 | 4/27/2024 |
tag: v3.1.0-alpha0