One problem with living on top of Java is calling into methods that
have no (conceptual) need to be synchronized. This could hurt
performance in an app carefully written in Clojure to avoid mutable
state and locking. Since unsynchronized PRNGs exist, I would suggest
we modify rand to use one. (I am willing to take the lead on writing
one in Clojure if needed.)
Thoughts?
Stuart
The docs for Math.random() say:
; When this method is first called, it creates a single
; new pseudorandom-number generator, exactly as if by
; the expression
;
; new java.util.Random
;
; This new pseudorandom-number generator is used
; thereafter for all calls to this method and is used
; nowhere else.
;
; This method is properly synchronized to allow correct
; use by more than one thread. However, if many threads
; need to generate pseudorandom numbers at a great rate,
; it may reduce contention for each thread to have its
; own pseudorandom-number generator.
So can't you just create multiple instances of java.util.Random if you
need that sort of thing?
--
Michael Wood <esio...@gmail.com>
While compare-and-swap is a lot better than a mutex, it's still not
free. IIRC a CAS is one (or maybe two) order(s) of magnitude more
expensive than a normal store. So a non-threadsafe PRNG should still
give you a performance boost compared to the CAS-version. But, as you said,
this is all speculation until someone writes some tests...
--
! Lauri
A Sun v. Sun comparison? What about JDK/JVM implementations from different vendors?
Dave
> I have the feeling that the "right interface" for a single stream of
> pseudorandom numbers is a seq, rather than a "function" that you
> call.
I'd provide two interfaces:
1) Low-level: (rng seed) yielding a pair [random-number, new-seed]
2) High-level: (random seed) yielding an infinite (obviously lazy)
seq of the random numbers obtained starting from the given seed.
What I consider important is the explicit specification of the seed,
which means that the code always says explicitly where its random
number sequence starts from.
I just happen to have those two functions in the example section of
my monad implementation, as rng works nicely in the state monad:
(defn rng [seed]
(let [m 259200
value (/ (float seed) (float m))
next (rem (+ 54773 (* 7141 seed)) m)]
(list value next)))
(defn random [seed]
(let [[value next] (rng seed)]
(lazy-cons value (random next))))
This particular implementation of rng is probably not a good one
(it's the first algorithm I found), but my point here is the
interface, not the algorithm.
Konrad.
If one plans to get very involved in the business of noise, I would
recommend exploring Boost's random library for C++. It provides an
array of entropy sources (MT, lagged fib, nondeterministic sources,
etc) and statistical distributions (uniform, normal, lognormal, and a
BUNCH more), and the user composes one of each to produce a generator.
I don't know how well the API would translate into a functional
programming style, but such a separation turns out to be fairly elegant.
Of course, it's probably also far too specialized to belong in the core!
As much flak as C++ gets, I feel like Boost goes a *long* way towards
making C++ productive and enjoyable for certain kinds of problems. There
are a lot of smart folks behind Boost and I personally wouldn't hesitate
to steal good ideas from them :)
-Kyle
Why don't you send it as an attachment?
> ...
>
> mfh
Randall Schulz
> So I'm going to stop pretending like I'm an expert and actually post
> some Clojure code. Be constructively critical 'cause I'm a n00b in
> that regard ;-) This is a pseudorandom number generator for the
> Gaussian (0,1) distribution.
...
Isn't that the standard Box-Muller transform?
Anyway, I tried to rewrite your code into a function that takes an
input stream of uniformly distributed random numbers and transforms
it into a stream of Gaussian random numbers. The advantage is that
there is no need to keep track explicitly of the state of the
calculation. In the following, I construct my input stream by
repeatedly calling Clojure's (rand), but you could easily substitute
any other source, including java.Util.Random.
Konrad.
(defn rng-uniform
"Return an infinite lazy sequence of random numbers"
[]
(drop 1 (iterate (fn [_] (rand)) nil)))
(defn transform-to-gaussian
"Transform a sequence of uniform random number in the interval [0, 1)
into a sequence of Gaussian random numbers."
[uniform-seq]
(let [[U1 U2 & uniform-rest] uniform-seq
V1 (- (* 2.0 U1) 1.0)
V2 (- (* 2.0 U2) 1.0)
S (+ (* V1 V1) (* V2 V2))
LS (. Math sqrt (/ (* -2.0 (. Math log S)) S))
X1 (* V1 LS)
X2 (* V2 LS)]
(if (or (>= S 1) (= S 0))
(recur uniform-rest)
(lazy-cons X1 (lazy-cons X2 (transform-to-gaussian uniform-
rest))))))
; Get 10 Gaussian random numbers
(take 10 (rng-gaussian))
> (defn rng-uniform
> "Return an infinite lazy sequence of random numbers"
> []
> (drop 1 (iterate (fn [_] (rand)) nil)))
>
> (defn transform-to-gaussian
> "Transform a sequence of uniform random number in the interval
> [0, 1)
> into a sequence of Gaussian random numbers."
> [uniform-seq]
> (let [[U1 U2 & uniform-rest] uniform-seq
> V1 (- (* 2.0 U1) 1.0)
> V2 (- (* 2.0 U2) 1.0)
> S (+ (* V1 V1) (* V2 V2))
> LS (. Math sqrt (/ (* -2.0 (. Math log S)) S))
> X1 (* V1 LS)
> X2 (* V2 LS)]
> (if (or (>= S 1) (= S 0))
> (recur uniform-rest)
> (lazy-cons X1 (lazy-cons X2 (transform-to-gaussian uniform-
> rest))))))
I forgot to copy one part:
(defn rng-gaussian
[]
(transform-to-gaussian (rng-uniform)))
>> (lazy-cons X1 (lazy-cons X2 (transform-to-gaussian uniform-
>> rest))))))
>
> Also much better -- the structs were ugly. Thanks for the nice
> revision!
In fact, this is a good test-case for the interface design for random-
number generators. If you want to be able to implement rejection-
based ones (which are numerous), there is a lot to gain from an
interface in terms of lazy sequences. The Box-Muller transform is
even more difficult because it transforms pairs of numbers. With any
other interface that would require some bookkeeping for managing the
additional state (first/second member of the pair), whereas a
sequence-based interface makes it nearly trivial.
Long live laziness! ;-)
> Do you think type hints on the double-floats could give some
> performance benefit? I'm curious where the type hints would go but
> too lazy to try the "add them everywhere and remove one by one until
> performance decreases" approach.
I have no experience at all with type hints. For example, I wonder if
Clojure already uses the calls to Math.log etc. to determine that
certain numbers must be floats. It would be nice to have tools that
show what type information the compiler already knows.
Konrad.