2009/03/12

The difference in random number generation in .NET

The next project I plan on working on is going to rely heavily on a Monte Carlo method of populating the initial state. Knowing this, I started digging into the different ways of generating random numbers in .NET. Everybody immediately goes with System.Random when they're starting out since it's readily visible in a new class file. This is all well and good, but you run into the issue that what it gives you is a pseudo-random number. It's actually pretty interesting to pop open Reflector and look at what it's doing under the hood. Granted it uses the system's ticks to seed the random number if you don't provide one yourself, but it does mean that processes (or even threads) creating an instance in the same tick will have the same random numbers. Well that can be a problem...

So how do we get a more *random* random number? That's where System.Security.Cryptography.RNGCryptoServiceProvider comes into the picture. Digging into this class it's making an external call, so not quite as easy to figure out how it's getting it's randomness. It really doesn't matter too much because it does come up with a better randomness. The drawbacks of using it though are that the way to work with it is quite a bit different than the System.Random class, and it is quite a bit slower. With the RNGCryptoServiceProvider you have to work with a byte array and do the conversions to whatever random type you want to work with. So if you build out your classes to use System.Random and later want to switch to RNGCryptoServiceProvider you have your work cut out for you. Or what about using RNGCryptoServiceProvider in production for "better" randomness, but use System.Random when you're unit/integration testing because you can control it? There isn't a common interface to use in that case, but I've been tinkering around getting such an interface set most of the day. Once I have it fleshed out more I'll post it up here.

Now back to the slowness of RNGCryptoServiceProvider vs. Random. Finding a few useful answers on Stack Overflow there was mention of it being slower, but never really gave any indication of how much worse it was. I've seen too many statements like this and the difference really only came down to maybe a few hundred milliseconds (so not a noticeable problem in the applications I've worked on). So I decided to toss together a test and find out just how much slower it is. Here's what I did to test it:

   1:  namespace ConsoleApplication1 
   2:  {  
   3:      using System; 
   4:      using System.Diagnostics; 
   5:      using System.Security.Cryptography; 
   6:    
   7:      class Program 
   8:      { 
   9:          static void Main(string[] args) 
  10:          { 
  11:              Stopwatch stopwatch = new Stopwatch(); 
  12:              RNGCryptoServiceProvider gen = new RNGCryptoServiceProvider(); 
  13:              Random generator = new Random(); 
  14:              //Since I'm only working with int for both randomizers... 
  15:              byte[] randomValues = new byte[ sizeof(Int32) ];  
  16:    
  17:              //step up the iterations by from 1 to 100,000,000; advancing by power of 10 
  18:              for (int iterations = 1; iterations <= Math.Pow(10,8); iterations *= 10)
  19:              { 
  20:                  Console.WriteLine(String.Format("Iterations: {0}", iterations));
  21:    
  22:                  //Start the RNGCryptoServiceProvider timing 
  23:                  stopwatch.Reset(); 
  24:                  stopwatch.Start(); 
  25:                  for (int i = 0; i < iterations; i++) 
  26:                  { 
  27:                      gen.GetBytes(randomValues); 
  28:                      BitConverter.ToInt32(randomValues, 0); 
  29:                  } 
  30:                  stopwatch.Stop(); 
  31:                  TimeSpan rngRandom = stopwatch.Elapsed; 
  32:                  Console.WriteLine( 
  33:                      String.Format("\tRNGCryptoServiceProvider:\t{0}", stopwatch.Elapsed)); 
  34:    
  35:                  //Start the System.Random timing 
  36:                  stopwatch.Reset(); 
  37:                  stopwatch.Start(); 
  38:                  for (int i = 0; i < iterations; i++) 
  39:                  { 
  40:                      generator.Next(); 
  41:                  } 
  42:                  stopwatch.Stop(); 
  43:                  TimeSpan sysRandom = stopwatch.Elapsed;
  44:                  Double speedFactor = Convert.ToDouble(rngRandom.Ticks) / Convert.ToDouble(sysRandom.Ticks); 
  45:                  Console.WriteLine( 
  46:                      String.Format("\tSystem.Random:           \t{0}\t~{1:0.00}x faster", 
  47:                                      stopwatch.Elapsed,
  48:                                      speedFactor));  
  49:                  Console.WriteLine(); 
  50:              } 
  51:              Console.ReadLine(); 
  52:          }  
  53:      }  
  54:  }

And here's the results:

image



The times varied a bit on my Core 2 Duo 2.50 GHz, 32-bit Vista laptop, but this isn't too far from the norm. It wasn't until 100,000 iterations that it took more that 1/10 of a second to finish, but at that point System.Random was ~279.2 times faster! The big thing is that even though the number of iterations are was growing by a factor of 10, RNGCryptoServiceProvider seemed to increase the time by more than a factor of 10.



So I guess the bottom line in the System.Random vs. RNGCryptoServiceProvider argument over slowness is in small numbers it doesn't greatly matter. Now if you're going to be generating more than 100,000 numbers in a very tight loop, it might be worth sacrificing "true" randomness with speed.

No comments: