From ec3b7c8758551e3c537fd3d0b59f703087e8efb2 Mon Sep 17 00:00:00 2001 From: Mark Sturgill Date: Sat, 5 Oct 2013 04:41:02 -0700 Subject: [PATCH] Aggressive rewrite of core random number generation. Add Random.cs to VS project. --- Server/Random.cs | 332 +++++++++++++++++++++++++++++++++++++++++++ Server/Server.csproj | 1 + Server/Utility.cs | 48 ++----- 3 files changed, 343 insertions(+), 38 deletions(-) create mode 100644 Server/Random.cs diff --git a/Server/Random.cs b/Server/Random.cs new file mode 100644 index 000000000..812aad1e4 --- /dev/null +++ b/Server/Random.cs @@ -0,0 +1,332 @@ +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Threading; + +namespace Server { + /// + /// Handles random number generation. + /// + public static class RandomImpl { + private static readonly IRandomImpl _Random; + + static RandomImpl() { + if ( Core.Unix ) { + _Random = new SimpleRandom(); + } else if (Core.Is64Bit && File.Exists("rdrand64.dll")) { + _Random = new RDRand64(); + } else if ( !Core.Is64Bit && File.Exists("rdrand32.dll") ) { + _Random = new RDRand32(); + } else { + _Random = new CSPRandom(); + } + + if (_Random is IHardwareRNG) { + if (!((IHardwareRNG)_Random).IsSupported()) { + _Random = new CSPRandom(); + } + } + } + + public static double NextDouble() { + return _Random.NextDouble(); + } + + public static int Next(int c) { + return _Random.Next(c); + } + + public static void NextBytes(byte[] b) { + _Random.NextBytes(b); + } + } + + public interface IRandomImpl { + double NextDouble(); + int Next(int c); + void NextBytes(byte[] b); + } + + public interface IHardwareRNG { + bool IsSupported(); + } + + public sealed class SimpleRandom : IRandomImpl { + private Random m_Random = new Random(); + + public SimpleRandom() { + } + + public double NextDouble() { + double r; + lock (m_Random) + r = m_Random.NextDouble(); + return r; + } + + public int Next(int c) { + int r; + lock (m_Random) + r = m_Random.Next(c); + return r; + } + + public void NextBytes(byte[] b) { + lock (m_Random) + m_Random.NextBytes(b); + } + } + + public sealed class CSPRandom : IRandomImpl { + private RNGCryptoServiceProvider _CSP = new RNGCryptoServiceProvider(); + + private static int BUFFER_SIZE = 0x4000; + private static int LARGE_REQUEST = BUFFER_SIZE / 0x100; + + private byte[] _Working = new byte[BUFFER_SIZE]; + private byte[] _Buffer = new byte[BUFFER_SIZE]; + + private int _Index = 0; + + private object _sync = new object(); + + public CSPRandom() { + _CSP.GetBytes(_Working); + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + private void CheckSwap(int c) { + lock (_sync) { + if (_Index + c < BUFFER_SIZE) + return; + + lock (_Buffer) { + byte[] b = _Working; + _Working = _Buffer; + _Buffer = b; + _Index = 0; + } + } + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + private void Fill(object o) { + lock (_Buffer) + lock (_CSP) + _CSP.GetBytes(_Buffer); + } + + private void _GetBytes(byte[] b) { + int c = b.Length; + + CheckSwap(c); + + lock (_sync) { + Buffer.BlockCopy(_Working, _Index, b, 0, c); + _Index += c; + } + } + + public double NextDouble() { + byte[] b = new byte[8]; + + _GetBytes(b); + + return (double)BitConverter.ToUInt64(b, 0) / ulong.MaxValue; + } + + public int Next(int c) { + return (int)(c * NextDouble()); + } + + public void NextBytes(byte[] b) { + int c = b.Length; + + if (c >= LARGE_REQUEST) { + lock (_CSP) + _CSP.GetBytes(b); + return; + } + _GetBytes(b); + } + } + + public sealed class RDRand32 : IRandomImpl, IHardwareRNG { + [DllImport("rdrand32")] + private static extern RDRandError rdrand_32(ref uint rand, bool retry); + + [DllImport("rdrand32")] + private static extern RDRandError rdrand_get_bytes(int n, byte[] buffer); + + private static int BUFFER_SIZE = 0x4000; + private static int LARGE_REQUEST = BUFFER_SIZE / 0x100; + + private byte[] _Working = new byte[BUFFER_SIZE]; + private byte[] _Buffer = new byte[BUFFER_SIZE]; + + private int _Index = 0; + + private object _sync = new object(); + + public RDRand32() { + rdrand_get_bytes(BUFFER_SIZE, _Working); + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + public bool IsSupported() { + uint r = 0; + return rdrand_32(ref r, true) == RDRandError.Success; + } + + private void CheckSwap(int c) { + lock (_sync) { + if (_Index + c < BUFFER_SIZE) + return; + + lock (_Buffer) { + byte[] b = _Working; + _Working = _Buffer; + _Buffer = b; + _Index = 0; + } + } + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + private void Fill(object o) { + lock (_Buffer) + rdrand_get_bytes(BUFFER_SIZE, _Buffer); + } + + private void _GetBytes(byte[] b) { + int c = b.Length; + + CheckSwap(c); + + lock (_sync) { + Buffer.BlockCopy(_Working, _Index, b, 0, c); + _Index += c; + } + } + + public double NextDouble() { + byte[] b = new byte[8]; + _GetBytes(b); + return (double)BitConverter.ToUInt64(b, 0) / ulong.MaxValue; + } + + public int Next(int c) { + return (int)(c * NextDouble()); + } + + public void NextBytes(byte[] b) { + int c = b.Length; + + if (c >= LARGE_REQUEST) { + rdrand_get_bytes(c, b); + return; + } + _GetBytes(b); + } + } + + public sealed class RDRand64 : IRandomImpl, IHardwareRNG { + [DllImport("rdrand64")] + private static extern RDRandError rdrand_64(ref ulong rand, bool retry); + + [DllImport("rdrand64")] + private static extern RDRandError rdrand_get_bytes(int n, byte[] buffer); + + private static int BUFFER_SIZE = 0x4000; + private static int LARGE_REQUEST = BUFFER_SIZE / 0x100; + + private byte[] _Working = new byte[BUFFER_SIZE]; + private byte[] _Buffer = new byte[BUFFER_SIZE]; + + private int _Index = 0; + + private object _sync = new object(); + + public RDRand64() { + rdrand_get_bytes(BUFFER_SIZE, _Working); + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + public bool IsSupported() { + ulong r = 0; + return rdrand_64(ref r, true) == RDRandError.Success; + } + + private void CheckSwap(int c) { + lock (_sync) { + if (_Index + c < BUFFER_SIZE) + return; + + lock (_Buffer) { + byte[] b = _Working; + _Working = _Buffer; + _Buffer = b; + _Index = 0; + } + } + ThreadPool.QueueUserWorkItem(new WaitCallback(Fill)); + } + + private void Fill(object o) { + lock (_Buffer) + rdrand_get_bytes(BUFFER_SIZE, _Buffer); + } + + private void _GetBytes(byte[] b) { + int c = b.Length; + + CheckSwap(c); + + lock (_sync) { + Buffer.BlockCopy(_Working, _Index, b, 0, c); + _Index += c; + } + } + + public double NextDouble() { + byte[] b = new byte[8]; + _GetBytes(b); + return (double)BitConverter.ToUInt64(b, 0) / ulong.MaxValue; + } + + public int Next(int c) { + return (int)(c * NextDouble()); + } + + public void NextBytes(byte[] b) { + int c = b.Length; + + if (c >= LARGE_REQUEST) { + rdrand_get_bytes(c, b); + return; + } + _GetBytes(b); + } + } + + public enum RDRandError : int { + Unknown = -4, + Unsupported = -3, + Supported = -2, + NotReady = -1, + + Failure = 0, + + Success = 1, + } +} \ No newline at end of file diff --git a/Server/Server.csproj b/Server/Server.csproj index a1883c89e..cf58d8dc8 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -150,6 +150,7 @@ + diff --git a/Server/Utility.cs b/Server/Utility.cs index 74503ddad..977b2926f 100644 --- a/Server/Utility.cs +++ b/Server/Utility.cs @@ -35,8 +35,6 @@ namespace Server { public static class Utility { - // TODO: ThreadLocal for .NET 4.0? - private static Random m_Random = new Random(); private static Encoding m_UTF8, m_UTF8WithEncoding; public static Encoding UTF8 @@ -777,11 +775,8 @@ namespace Server { int total = 0; - lock (m_Random) - { - for (int i = 0; i < numDice; ++i) - total += m_Random.Next(numSides) + 1; - } + for (int i = 0; i < numDice; ++i) + total += RandomImpl.Next(numSides) + 1; total += bonus; return total; @@ -789,18 +784,12 @@ namespace Server public static int RandomList( params int[] list ) { - int r; - lock (m_Random) - r = m_Random.Next( list.Length ); - return list[r]; + return list[RandomImpl.Next(list.Length)]; } public static bool RandomBool() { - bool r; - lock (m_Random) - r = ( m_Random.Next( 2 ) == 0 ); - return r; + return (RandomImpl.Next(2) == 0); } public static int RandomMinMax( int min, int max ) @@ -816,11 +805,7 @@ namespace Server return min; } - int r; - lock (m_Random) - r = m_Random.Next( (max - min) + 1 ); - - return min + r; + return min + RandomImpl.Next((max - min) + 1); } public static int Random( int from, int count ) @@ -831,40 +816,27 @@ namespace Server } else if ( count > 0 ) { - int r; - lock (m_Random) - r = m_Random.Next( count ); - return from + r; + return from + RandomImpl.Next(count); } else { - int r; - lock (m_Random) - r = m_Random.Next( -count ); - return from - r; + return from - RandomImpl.Next(-count); } } public static int Random( int count ) { - int r; - lock (m_Random) - r = m_Random.Next( count ); - return r; + return RandomImpl.Next(count); } public static void RandomBytes( byte[] buffer ) { - lock (m_Random) - m_Random.NextBytes(buffer); + RandomImpl.NextBytes(buffer); } public static double RandomDouble() { - double r; - lock (m_Random) - r = m_Random.NextDouble(); - return r; + return RandomImpl.NextDouble(); } #endregion