rmandvikar.Random2 3.1.0-alpha0

Prefix Reserved
This is a prerelease version of rmandvikar.Random2.
This package has a SemVer 2.0.0 package version: 3.1.0-alpha0+4f2607b3767e54e031c70fa0e9842c4321638d0b.
dotnet add package rmandvikar.Random2 --version 3.1.0-alpha0                
NuGet\Install-Package rmandvikar.Random2 -Version 3.1.0-alpha0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="rmandvikar.Random2" Version="3.1.0-alpha0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add rmandvikar.Random2 --version 3.1.0-alpha0                
#r "nuget: rmandvikar.Random2, 3.1.0-alpha0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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.

NuGet version (rm.Random2)

Background

See: https://csharpindepth.com/articles/Random

TLDR, Random in C# has 2 problems that developers have to code around:

  1. 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).
  2. 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 on net6.0+. It perfs similarly to ThreadStaticRandom 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's GetHashCode() implementation.
  • Implementation with global Random instance is riskiest (issue 1. above), so definitely avoid that.
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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 79 4/27/2024

tag: v3.1.0-alpha0