RANDOM, and initialization
Hello, lispers!
I ran into an interesting problem, recently, which I traced
down to RANDOM being initialized from, as far as I can tell,
GET-UNIVERSAL-TIME (or equivalent), to 1 second resolution.
I delivered a toy app which runs the code below,
and, did this at the prompt:
% random-test "/tmp/a" 1000 &; random-test "/tmp/b" 1000 &;
then diffed a and b, and got exactly the same file twice.
Trying to ensure that my multiple apps don't all start within
the same second seems very hackish; I'd rather just be able
to seed the generator with something I know will be unique to
each process.
Now, sadly, the hyperspec for MAKE-RANDOM-STATE states:
"If state is t, the new-state is a fresh random state object that has
been randomly initialized by some means."
i.e. there appears to be no portable way to force a new fresh random
state initialized by something more sensible; e.g. a combination
of the current time to microsecond accuracy, + current PID, plus
a few bytes gathered from /dev/urandom, to pick something off the
top of my head which is a bit better than just GET-UNIVERSAL-TIME :-)
I also noted that LW provides this cool thing called a "Mersenne Twister"
random number generator, which Wikipaedia assures me is the thing
to use (as long as you don't need cryptographically secure numbers,
which I don't), but it's interface is exactly copied from the spec's
MAKE-RANDOM-STATE.
Ideally, I'd be able to reseed the generator with a seed containing
a lot more entropy than just a universal time.
How do others handle this? Do I need to roll my own, or is there
some internal, undocumented API to set the seed of either of those
generators to a new value?
Also, if MT random numbers are "better" than the (I assume, simple
linear congruence relation) numbers generated by CL:RANDOM, why isn't
CL:RANDOM just implemented in terms of MT randoms?
Any thoughts will be appreciated.
Alain Picard
Example code -- on LWL 4.4.6, this usually can be made
to regenerate exactly the same sequence of numbers twice.
================================================================
(in-package :user)
(defconstant +random-no-tests-version+ "$Revision: 1.5 $"
"$Id: lisp-insert.lisp,v 1.5 2005/10/18 01:52:04 kooks Exp $
Report bugs to: bugs@memetrics.com")
(defun gen-guid ()
(let ((part1 (random #xffffffff))
(part2 (random #xffffffff))
(part3 (random #xffffffff)))
(format nil "UID~{{~8,'0x}~}" (list part1 part2 part3))))
(defun dump-some-guids (path n)
(with-open-file (stream path :direction :output :if-exists :append :if-does-not-exist :create)
(dotimes (i n)
(format stream "~&~A" (gen-guid))))
T)
(defun main ()
(destructuring-bind (program path ntimes) sys:*line-arguments-list*
(dump-some-guids path (read-from-string ntimes))))
================================================================
--
Please read about why Top Posting
is evil at: http://en.wikipedia.org/wiki/Top-posting
and http://www.dickalba.demon.co.uk/usenet/guide/faq_topp.html
Please read about why HTML in email is evil at: http://www.birdhouse.org/etc/evilmail.html
Re: RANDOM, and initialization
Alain Picard <Alain.Picard@memetrics.com> writes:
> I ran into an interesting problem, recently, which I traced
> down to RANDOM being initialized from, as far as I can tell,
> GET-UNIVERSAL-TIME (or equivalent), to 1 second resolution.
Two data points:
- Happy Mac: Running your test app on my Mac using the 32 bit version
of LispWorks 5.0.2 always produces two files with different
contents. (Where "always" was about 20 runs).
- Sad Linux: Running your test app on my Linux box using the 64 bit
version of LispWorks 5.0.2 mostly produces two files with the same
contents. (Where "mostly" was about 90% of 20 or so runs).
> How do others handle this? Do I need to roll my own, or is there
> some internal, undocumented API to set the seed of either of those
> generators to a new value?
I generally roll my own and read bytes directly from the /dev/urandom
or /dev/random file. It's probably not very fast and certainly not
portable, but it seems to work for my application.
A slightly massaged code snippet:
(defparameter *random-device* "/dev/urandom")
(defun get-random-bytes (nbytes)
(with-input-from-file (in *random-device* :element-type '(unsigned-byte 8))
(iter (repeat nbytes)
(collect (logxor (read-byte in)
(random 256))))))
(defconstant "~(~2,'0x~2,'0x~2,'0x~2,'0x-~2,'0x~2,'0x-~2,'0x~2,'0x-~2,'0x~2,'0x-~2,'0x~2,'0x~2,'0x~2,'0x~2,'0x~2,'0x~)")
(defun generate-uuid ()
"Generate a Universally Unique Identifier using the same logic
as the uuid-gen program and library."
(apply #'format nil +uuid-format+ (get-random-bytes (/ 128 8))))
(generate-uuid) => "fc42bb48-eecb-7106-27dd-9312b0aaddf4"
(generate-uuid) => "973d063e-e2e8-a480-443e-9fc22f993b1f"
(generate-uuid) => "5979069c-f5ad-7a31-752e-d463d7dd4e1d"
Cheers,
Chris Dean