Print

Print


On Jan 17, 2008, at 5:13 AM, Michael Wilson wrote:

> If you create several instances in quick succession you get identical
> RNGs, and System.currentTimeMillis() doesn't have much entropy anyway.

I think this notion betrays a bit of cargo-cult programming, and it's  
worth explaining why.


>   private long defaultSeed() {
>
>     final long xseed = ((iseed*ISEED_MULT)+ISEED_INC)&ISEED_MASK;
>     final long nanos = System.nanoTime(); iseed = xseed;
>     return (nanos>>>32)^(nanos<<32)^System.currentTimeMillis()^
>            xseed^(((long)super.hashCode())<<32)^
>            Runtime.getRuntime().freeMemory();
> }
>
> It combines entropy from the current real time, the number of clock
> ticks since last startup, the new object's memory address (via  
> super.hashCode()), the amount of free memory in the JVM and the
> output of an embedded static RNG which uses the same algorithm as
> java.util.Random.

It's a cute function overall, but I have a few nits;

	- nanoTime is not available on earlier Java versions, *and* it's not  
guaranteed to work at all.  It's probably not a good idea to use.
	- hashCode is the wrong function.  It should be  
System.identityHashCode(...).  It's also not as random as you think:  
it's word aligned and sequentially packed on some systems.


> This produces a reasonable amount of entropy
> for experimental purposes (we have a couple of 'high quality' seeding
> methods too but they take longer to run).

I think this may be mistaken.  It's true that if you used a crummy  
generator with a small seed (such as c's rand()), providing a good  
entropy seed would be important.    But a crummy generator MT is  
not.   Generally speaking, providing seed entropy shouldn't have  
almost any effect at all.  It's waving a dead chicken.

Mersenne Twister has an internal state of 624 longs.  If you fill  
those longs with a poor-entropy value, or successive poor-entropy  
values, after just a few pumps (probably just one pump), you will  
*not* be able to notice from the MT output.  It will be so random as  
to pass every known statistical test.  This is one reason why MT is  
so well respected.  (Eith one exception: if you have a *lot* of 0's  
in your internal state, MT takes a while to warm up).

Even if you're still not willing to trust the math, we're not loading  
the internal state with low-entropy values.  We're loading it with  
relatively high-entropy values generated as the stream output of a  
carefully-selected linear congruential generator whose seed in turn  
is set to the initial 32-bit number you provide.  Ordinarily linear  
congruential generators aren't fantastic to use as random number  
generators for experiments, but they're just fine for providing  
enough entropy to MT so that those cargo-cult programmers who don't  
trust MT theorists can feel like you've waved the necessary dead  
chickens.

This means that you should be able to use seeds like this:

	2345, 2346, 2347, ...

and have pretty high-quality randomly different experimental  
results.  So you're likely not accomplishing anything with your super- 
duper seeder above, other than feeling warm and fuzzy.  If you have  
statistical results that counter this, I (and I think a fair degree  
of the RNG community) would really like to see them published!  (no  
really! I'm not trying to be sarcastic.  What do you have?)

Now as to why we have the defaultSeed(), setSeed(), and 32-bit seed  
choices: because they are the standard for MT19937.  See here:
	http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/ 
mt19937ar.c
	http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/emt19937ar.html

We chose to be 100% consistent with the MT standard rather than risk  
wandering off the ranch in search of a seed twice as long (which only  
benefits you if you need to do more than 2^32 experiments).  If you'd  
like something else, you should just go in and set all 624 initial  
values in the MT array: there's a function for that.  Otherwise,  
there are *big* benefits to having a standard RNG.

32 bits, rather than a longg is not a big deal either.  After all,  
java.util.Random uses a only 48-bit seed rather than a long (and in  
fact that's its entire internal state!  java.util.Random is very poor).

Now: how to initialize RNGs in sequence?  Assuming you let MT's  
default seeder do its job, the answer then doesn't lie in seeding  
entropy, but in simply guaranteeing that you have a different seed  
each time.  Just grab System.currentTimeMillis(), use it for your  
first seed, then use the same value + C for your next seed, then the  
next value + 2C for your next seed, then +3C, etc., where C is some  
integer > 0.  Just maintain that internal counter somewhere and you  
should be fine.

Sean