|
Introduction
Here I present a class that can be substituted in place for the the .NET framework's System.Random class to provide some advantages:
- Based on a simple and fast XOR-shift pseudo random number generator (RNG) specified in the paper: Marsaglia, George. (2003). Xorshift RNGs). This particular implementation of XOR-shift has a period of 2^128-1. See the above paper to see how this can be easily extended if you need a longer period. At the time of writing, I could find no information on the period of
System.Random for comparison.
- Faster than
System.Random. Up to 8x faster, depending on which methods are called and which CLR is used (see table below).
- Direct replacement for
System.Random. This class implements all of the methods that System.Random does plus some additional methods for generating random uints and booleans. The like named methods are functionally equivalent to those in System.Random.
- Allows fast re-initialization with a seed, unlike
System.Random which accepts a seed at construction time only, which then executes a relatively expensive initialization routine. This provides a vast speed improvement if you need to reset the pseudo-random number sequence many times, e.g., if you want to re-generate the same sequence many times. An alternative might be to cache random numbers in an array, but that approach is limited by memory capacity and the fact that you may also want a large number of different sequences cached. Each sequence can be represented by a single seed value (int).
Background
I created FastRandom in order to achieve greater speed in a prey capture simulation within another project, SharpNEAT. That simulation requires that the RNG be reset with a given seed 1000s of times per second. FastRandom's Reinitialise() methods, therefore, provide a nice performance boost over System.Random in that case. I then discovered that a number of further performance improvements could be made to the Next*() methods. The first version of FastRandom posted on CodeProject used a multiply-with-carry (MWC) algorithm devised by George Marsaglia. Forum posters pointed out that some seeds generated a sequence of the same number, and whilst investigating the solution, I came across another of Marsaglia's algorithms utilizing an XOR-shift technique that was even faster than MWC. The current version of FastRandom therefore implements XOR-shift and should also provide good random sequences for all seed values (including 0).
The Math
The random number generator (RNG) used generates new numbers using just bitwise XOR and left and right shifts. The method NextUInt provides the simplest example because it returns the generated 32 bit number (uint) without any further manipulation: public uint NextUInt()
{
uint t= (x^(x<<11));
x=y;
y=z;
z=w;
return (w= (w^(w>>19))^(t^(t>>8)));
}
The state of the RNG is described by the four uint variables x, y, z and w. w represents most recently generated number, and a history of the last four generated numbers is maintained with the inclusion of the x, y and z variables. New numbers are generated by applying various shifts and XORs to x, which represents the number generated four calls ago. Storing and using the history of the last four numbers in this manner results in an RNG with a longer period, here the period is 2^128-1. The period can be shortened or lengthened by adjusting the amount of history variables stored. For more information on this, see the paper referred to above.
All of the other Next*() methods are variations of this technique, taking the 32 bits generated and manipulating them into double, int, bytes, etc.
Reinitialise() methods
The Reinitialise methods allow the caller to reset FastRandom with a single integer seed value and thus generate the same set of random numbers over again. This can sometimes be useful, e.g., in simulations where you might want to recreate the same scenario exactly as before. Note that System.Random provides no such method for re-initializing (re-seeding) the class once it is constructed; the only option is to construct a new instance and pass the seed in to the constructor, which then executes code to build an array of seed data. By allowing re-initialization and avoiding the need to build a seed data array, FastRandom provides a significant performance improvement where reseeding is required.
Other Performance Improvements (in comparison to System.Random)
- Avoid use of floating point arithmetic where possible. This applies to
Next() and NextBytes(byte[]).
- Where floating point arithmetic is used, ensure that casts are performed from
int to double, and not from uint to double. In tests, casting from uint took twice as long as casting from int. This speed-up applies to NextDouble(), Next(int) and Next(int,int).
- Don't declare methods as
virtual. The virtual method table generates some overhead even in released, optimized code where the methods haven't actually been overridden. System.Random's methods are declared as virtual and therefore generate this overhead. There may be sound reasons for this within the .NET framework, but if you just want a fast RNG today, then we can omit the virtual keyword in our declarations.
- In the
NextBytes method, we generate 32 bits at a time and fill the byte array in 4 byte chunks.
Performance Comparison Table
For prior readers of this article please note that this is an updated version of the table that takes into account improvements made to FastRandom.cs made since the article was first posted and also to the .NET runtime engine between .NET 1.1 and .NET 2.0.
Other notes:
- Both
FastRandom and System.Random run faster on the .NET 2.0 CLR than on .NET 1.1. However, System.Random does benefit more than FastRandom and so the performance gap between the two classes is narrower in .NET 2.0.
- One exception to the above point is
Next(int,int) with a long range between the two integer parameters, on the .Net 1.1 CLR FastRandom's version actually ran slower, however on .NET 2.0 this result is now reversed as can be seen in the table below.
The following performance figures were obtained using released, optimized code executing on an Intel Core 2 Duo E660 overclocked to 3.11Ghz. This is a dual core chip, however these performance figures are for a single core only:
| |
System.Random (millions calls/sec)
|
FastRandom (millions calls/sec)
|
Speed increase |
Next() |
103.252 |
220.750 |
2.14x |
Next(int) |
51.826 |
142.247 |
2.14x |
Next(int,int) |
34.506 |
87.680 |
2.54x |
Next(int,int) <long range>* |
16.182 |
30.261 |
1.87x |
NextDouble() |
87.680 |
185.528 |
2.12x |
NextBytes() 1024 byte array in tests |
0.105 |
0.927 |
8.83x |
NextUInt() |
n/a |
261.437 |
n/a |
NextInt() |
n/a |
256.081 |
n/a |
NextBool() |
n/a |
312.500 |
n/a |
* - An alternative execution path occurs when the range between the lower and upper limits will not fit within an int. This results in a different performance figure.
Note the last three methods which are extra methods not present on System.Random. NextUint() is provided because uint is the underlying data type behind FastRandom and so is very fast to generate. NextInt() returns an int (Int32<?CODE>) but unlike Next() the range is between 0 and int.MaxValue instead of between 0 and int.MaxValue-1. This subtle difference allows an optimization to be made (elimination of an 'if' statement). NextBool() is implemented by generating 32 bits (uint) and buffering them for future calls, hence the high speed.
Conclusion
System.Random is actually very fast and achieves its speed mostly by only using simple and fast arithmetic operations such as shift and add. However, the whole class is based around a central Sample() method that returns a double between 0.0 and 1.0, and thus there is some unnecessary floating point arithmetic used to generate integer values. FastRandom utilizes a completely different algorithm for generating random numbers that is inherently slightly faster, and in FastRandom we provide a further boost by avoiding floating point arithmetic wherever possible and implementing some further refinements. Finally, FastRandom also allows for fast re-seeding which allows repeat random number sequences to be re-generated very quickly.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 32 (Total in Forum: 32) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I don't care for speed. I'll use your pseudo-random number generator for a platform independent project, because I need to generate a sequence of numbers for a given seed again and again. The implementations of System.Random in .NET and mono return different sequences for the same seed, so my application would not really do the same on every platform. Thanks for this nice alternative!
_____________________________________________________ This statement is false.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Colin,
I've recently developed a managed library for random number generators and distributions and published it here[^] on CodeProject. I'd known your article (greate work by the way) for some time and decided to include a XorShift RNG. As you stated in a previous post that you're happy for your code to be reused, I took your FastRandom class as basis for my implementation which of course includes your summary and a reference to your article. When doing this, I've found a minor "error" and a performance improvement.
1.) The comment for the Next method states that "Values returned are over the range 0 to int.MaxValue-1". But int.MaxValue (0x7FFFFFFF) can also occur (tested it) as your code generates a uint with full range and then does a bitwise AND with 0x7FFFFFFF. So int.MaxValue is returned when the unsigned integer 0xFFFFFFFF is generated. If added a simple if-else that returns the generated integer if its unequal int.MaxValue or generates a new one otherwise.
2.) In the NextBytes method it's possible to leave out the bitwise AND of w and e.g. 0x000000FF before casting to byte as the cast only involves the rightmost 8 bits and doesn't care for the rest.
buffer[i++] = (byte)( w&0x000000FF); --> buffer[i++] = (byte)w; buffer[i++] = (byte)((w&0x0000FF00) >> 8); --> buffer[i++] = (byte)(w >> 8);
Best regards, Stefan
"Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook www.troschuetz.de
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Stefan,
Thanks for the feedback and apologies for not responding sooner. Yeh sure no problem with re-using the code in your library. The librray looks very useful by the way. In the past when I've wanted to generate number sequences with different distributions I've had to resort to hacking/porting parts of randlib from C, which in turn was ported from Fortran I think and quite frankly that code is a mess!
With regard to point (1), I think the better solution would be simply to AND with 0x7FFFFFFE instead of 0x7FFFFFFF. My initial thought was that this would generate an uneven distribution of generated numbers, but of course each bit is random independently of all other bits so that masking out some bits still leaves an even distribution.
On point (2), thanks for this, I tried it and it is indeed functionally equivalent and also about 6% faster in the test I did.
I'm about to update the article and the file with these two changes, plus an update to the performance table as I noticed the figures are all out by a factor of 1000! Also the performance of System.Random and FastRandom under .Net 2.0 is very different than under .Net 1.1. FastRandom still wins though 
Regards
Colin
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello again!
colgreen wrote: apologies for not responding sooner
No problem I'm very patient 
colgreen wrote: I think the better solution would be simply to AND with 0x7FFFFFFE instead of 0x7FFFFFFF
I thought about this one too, but it has a major drawback: the rightmost bit will always be zero and therefor only even numbers will be generated. Although they may be evenly distributed, I think this isn't very practical and favor using the if-else-approach.
Best Regards, Stefan
"Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning." - Rick Cook www.troschuetz.de
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
i needed a fast source for random bytes for filling files ans drives with random bytes. therefore i tried to optimize your code a bit. i measured the performance after each step multiple times with realtime priority. i noticed that reversing the loop (from length - 1 to 0) increased throughput considerably. setting i to uint improved perf by 1%. i unrolled the loop 4 times to allow for out-of-order-execution and to reduce the impact of the loop code. faster code was generated, when i used uintptr[i] instead of *uintptr++.
public unsafe void GetBytes(byte* buffer, int length) { uint x = this.x, y = this.y, z = this.z, w = this.w; uint t;
uint* uintptr = (uint*)buffer; length /= 4; for (uint i = (uint)(length - 4); ; i -= 4) { t = x ^ (x << 11); x = y; y = z; z = w; w ^= t ^ (w >> 19) ^ (t >> 8); uintptr[i] = w;
t = x ^ (x << 11); x = y; y = z; z = w; w ^= t ^ (w >> 19) ^ (t >> 8); uintptr[i + 1] = w;
t = x ^ (x << 11); x = y; y = z; z = w; w ^= t ^ (w >> 19) ^ (t >> 8); uintptr[i + 2] = w;
t = x ^ (x << 11); x = y; y = z; z = w; w ^= t ^ (w >> 19) ^ (t >> 8); uintptr[i + 3] = w;
if (i == 0) { break; } } this.x = x; this.y = y; this.z = z; this.w = w; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
We are developing an open source cross-platform product on Mono and DotNET. We are in need of a portable Random Number generator like the one published here (http://www.codeproject.com/csharp/fastrandom.asp). What legal licenses, if any, are placed on this code?
jnorman
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi jnorman,
I suppose the answer is that there isn't one (a license), apart from whatever I agreed to when I posted the article. However, I'm happy for the code to be reused in whatever context so if you want I can send a modified version of the source with extra license blurb and get them to update the zip file on here.
Right now I'm thinking of switching over my other project (sharpneat) to a license less strict and more legally digestable than the L(GPL) - Something like the MIT or Apache licenses.
http://www.opensource.org/licenses/mit-license.php
I take it that would be ok? 
Colin.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Reinitializing with the same seed should produce the same random sequence. Try this:
FastRandom r = new FastRandom(); r.Reinitialise(0); int r1 = r.Next(); int r2 = r.Next(); r.Reinitialise(0); int r3 = r.Next();
r1 and r3 should be the same number. I think you need to add this to the Reinitialise function:
y = 842502087; z = 3579807591; w = 273326509;
Thanks for the great article,
Jeremy
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Nice article!
I wrote an additional routine, NextBytesUnsafe, that does NextBytes but with pointer arithmetic, and copies the w, x, y and z fields to local variables to speed it up even more.
Since w is already a random uint 4 bytes long, the routine copies the whole thing, and increments the pointer 4 bytes at a time.
The two downsides are that it needs /unsafe, and that the input buffer size must be divisble by 4.
Here's the code:
public unsafe void NextBytesUnsafe(byte[] buffer) { if(buffer.Length % 4 != 0) throw new ArgumentException("Buffer length must be divisible by 4", "buffer");
uint x = this.x, y = this.y, z = this.z, w = this.w; uint t;
fixed(byte* pByte0 = buffer) { uint* pDWord = (uint*)pByte0; for(int i = 0, len = buffer.Length/4; i < len; i++) { t=(x^(x<<11)); x=y; y=z; z=w; *pDWord++ = w = (w^(w>>19))^(t^(t>>8)); } }
this.x = x; this.y = y; this.z = z; this.w = w; }
On my Pentium 4 2.4ghz HT machine, the original implementation takes 3.078 seconds to generate 1024 random bytes 1,000,000 times, while the unsafe implementation takes 1.406 seconds -- about 2 times faster.
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
Hi,
Brilliant! Thanks for the feedback and the extra NextBytes method, I hadn't considered this approach at all so I learned something today 
Just to confirm for the benefit of others, I looked over the new method and it checks out and does indeed run over 2x faster than the safe method. A few additional points then:
1) When i revisited the performance tests I noticed that the unchanged safe methods ran noticably slower with the /unsafe compilation flag on. Worth noting.
2) The technique of copying the class variables into local variables does indeed have significant benefit because the variables are heavily used within the main loop. However the same technique can also be applied to the safe version of NextBytes, doing so increases speed from about 389 calls/sec to 450 calls/sec on my AMD Athlon 2400+ (with /unsafe switched off). Still a lot slower than the unsafe method but of benefit if you can't or dont want to use the unsafe method.
3) The divide by 4 in the new method can be achieved ever so slightly more efficiently with a shift-to-right by 2 bits operation, e.g. for(int i = 0, len = buffer.Length>>2; i < len; i++)
but this is only executed once per call to the method so it doesn't have any noticeable effect on performance. Even so, I always like to squeeze out as many clock cycles as possible from a simple re-usable routine like this - Yes I know that optimisation is the root of all evil 
Thanks again,
Colin.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
colgreen wrote: Still a lot slower than the unsafe method but of benefit if you can't or dont want to use the unsafe method.
Sorry my bad, didn't mean to confuse the local copies idea with using /unsafe 
By the way, I'm writing a realtime cloud generator right now, and your article really saved the day for me.
Thanks again!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I know that there probably isn't, but is there an equivalent for this method (NextBytesUnsafe) for VB? If not, I'll just use C#. (And yes, I know VB doesn't have equivalents for pointers and that is why there probably isn't a VB equivalent for the code)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Nope, that's one of the very few things C# can do that VB can't.
You could compile the class in C# and add a reference to it from VB though... not sure how that'd affect performance but maybe it's worth a try.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
To address a problem with seeding I have now re-coded FastRandom to use a completely different algorithm, namely an xor-shift method. Although there was a much simpler fix for the problem, I found this new algorithm a little faster so I decided to switch.
The algorithm itself is explained in a paper linked from the top of the article.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for the reference.
I have one remaining suggestion: inherit from System.Random and override methods as appropriate. This will truly make your class a direct replacement for System.Random.
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein Numerical components for C# and VB.NET
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Perhaps I'll leave that as an exercise for the reader to do if and when they need a direct replacement e.g. if some binary library method only accepts System.Random objects.
Non-virtual methods have ever-so-slightly more overhead than virtual ones, so the current arrangement gives the fastest possible speed.
Thanks for the ongoing feedback BTW. And I did spot the 'Marsaglia effect' in the link you posted
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello
Why do you not use one of the standard algorithms that are proofed to behave randomly. (I can't remember the name of THE standard algorithm that uses simple multiplication and modulo)
Nevertheless I think it's a good and efficient algorithm for random numbers 
Aloha Urs
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
As I understand it the algorithm used is proven to be random and to have a long period. With regard to the short period of 1 problem, George Marsaglia stipulates some restrictions on the seeds values that should be used. However, I have now re-coded the FastRandom class to use another of Marsaglias's algorithms that is even faster than multiply-and-carry. This new algorithm is based on XOR and bit shifting only and does not exhibit the same problem with the Reinitialise methods.
Just waiting for the Codeproject folks to update the article 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Please define "proven to be random".
I would like to note some references on rngs:
D. E. Knuth's "The Art of Computer Programming" gives an extensive discussion on (mostly linear) random number generators.
Theory and Practice of Random Number Generation by Peter Hellekalek at the Mathematics Department of the University of Salzburg is a good starting point for practitioneers.
Jens
-- Jens Scheidtmann
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well ok. The algorithm is proven to provide a good pseudo-random sequence of long period. Please see George Marsaglia's paper (linked in the article) and general body of work on RNGs for more info on tests of randomness, and mathemetical basis of this RNG, etc.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Urs Enzler wrote: Why do you not use one of the standard algorithms that are proofed to behave randomly. (I can't remember the name of THE standard algorithm that uses simple multiplication and modulo)
I believe you're talking about the Linear Congruential method. The algorithm in the original article used a combination of two such RNG's. Whatever method you use, you must be careful to verify that the sequence you generate works well using the parameters you chose. A bad choice of parameters can destroy the appearance of randomness.
'Standard' is not always better. The RANDU random number generator was published by IBM in the 60's and something of a standard back then. It is of the linear congruential type. However, it was later discovered that it wasn't all that good. See this page[^] for an illustration (Java applet).
Constructing pseudo-random number generators is a bit of an art, it seems. There is a whole range of algorithms out there, with a large range of speed and quality.
Which algorithm you use depends on your application. For example, cryptography requires much better randomness than a simple game because the whole point of cryptography is to destroy any appearance of a pattern or relationship between the encrypted data and the original. In simulations, a pattern like that produced by the RANDU algorithm can introduce a bias that taints the results.
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein Numerical components for C# and VB.NET
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You are absolutely right. With 'standard' I meant an algorithm that is proven to be good.
The algorithm you mentioned is indeed very famous because a lot of scientific work had to be thrown away because of it On the other hand some simulation proofed to be very stable even against a very bad random number generator.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
colgreen wrote: That simulation requires that the RNG be reset with a given seed 1000s of times per second. FastRandom's Reinitialise() methods therefore provide a nice performance boost over System.Random in that case. I then discovered that a number of further performance improvements could be made to the Next*() methods.
How many random numbers do actually generate within the loop? Wouldn't it be a lot faster if you created the sequence outside the loop, put them in an array, and inside the loop just picked off numbers from that array?? You may be able to do some pre-processing of these random values outside the loop, leading to an even greater speed-up.
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein Numerical components for C# and VB.NET
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, Yes that is an option but some simulations are open ended with regards to their end condition and so could require millions or billions of rnd numbers. But yes, for the more common requirement of say a few thousand numbers caching numbers would be faster, in fact I might try this out.
Thanks for the suggestion.
Colin
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|