Affects Version/s: Release 1.6
Fix Version/s: None
Patch:Code and Test
Clojure's random functions currently use Math.random and related features, which makes them impossible to seed. This seems like an appropriate use of a dynamic var (compared to extra arguments), since library code that wants to behave randomly could transparently support seeding without any extra effort.
I propose (def ^:dynamic *rand* (java.util.Random.)) in clojure.core, and that rand, rand-int, rand-nth, and shuffle be updated to use *rand*.
I think semantically this will not be a breaking change.
I did some benchmarking to try to get an idea of the performance implications of using a dynamic var, as well as to measure the changes to concurrent access.
The code used is at https://github.com/gfredericks/clj-1452-tests; the raw output is in a comment.
rand is slightly slower, while shuffle is insignificantly faster. Using shuffle from 8 threads is insignificantly slower, but switching to a ThreadLocalRandom manually in the patched version results in a 2.5x speedup.
Running on my 8 core Linode VM:
|Benchmark||Clojure||Runtime mean||Runtime std dev|
|rand||1.6.0 + *rand*||63.7ns||1.80ns|
|shuffle||1.6.0 + *rand*||12.8µs||241ns|
|threaded-shuffling||1.6.0 + *rand*||152ms||8.77ms|
|threaded-local-shuffling||1.6.0 + *rand*||64.5ms||1.41ms|
Approach: create a dynamic var *rand* and update rand, rand-int, rand-nth, and shuffle to use *rand*