Lisp HUG Maillist Archive

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


Updated at: 2020-12-10 08:46 UTC