I know I can pass RNG to many (all?) stochastic functions, and that StableRNG is slow, but is there a way I can set at the beginning of my model something like:
random_seed = 123 # from a config file
Random.GLOBAL_RNG = StableRNG(123)`
?
This doesn’t work as Random.GLOBAL_RNG is a constant.
And if it’s too tedious to pass down the call chain, you could make your RNG a global const and just access it at the call sites. Although if it’s not then passing it down is a tad nicer.
One could use a ScopedValue for the best of both worlds. It would be nice if the default rng was a ScopedValue but that’s probably infeasible as the default rng is very special and gets seeded on task creation for reproducible randomness with multi threading.
@btime pi_default($N);
2.505 s (0 allocations: 0 bytes)
@btime pi_global($N);
2.524 s (0 allocations: 0 bytes)
@btime pi_local($N, $rng);
2.511 s (0 allocations: 0 bytes)
@btime pi_local($N, $stablerng);
1.394 s (0 allocations: 0 bytes)
Code
using Random
using StableRNGs
using BenchmarkTools
const MY_RNG = Xoshiro()
function pi_default(n::Int)
s = 0
for _ in 1:n
s += rand()^2 + rand()^2 < 1
end
return 4 * s / n
end
function pi_global(n::Int)
s = 0
for _ in 1:n
s += rand(MY_RNG)^2 + rand(MY_RNG)^2 < 1
end
return 4 * s / n
end
function pi_local(n::Int, rng::AbstractRNG)
s = 0
for _ in 1:n
s += rand(rng)^2 + rand(rng)^2 < 1
end
return 4 * s / n
end
N = 10^9
rng = Xoshiro()
stablerng = StableRNG(123)
@btime pi_default($N);
@btime pi_global($N);
@btime pi_local($N, $rng);
@btime pi_local($N, $stablerng);
Yes, for me StableRNGs are slightly faster in that case.
However, the story is different when generating large-ish arrays of random numbers, as it seems the default rng has a good SIMD implementation for that case.
Separate from the Random vs StableRNGs performance comparison:
One factor making RNGs slow when generating just a few random numbers in a call is that they’re mutable. Reading/writing the RNG state to RAM incurs some overhead for every rand call (not every number that’s generated, which is why only small calls suffer). An immutable RNG state (implemented like an iterator, for example, where the state is carried as a local variable) would not need to do this, so would be faster for small calls (orthogonal to the SIMD question).
It’s been on my list (long and probably never to be completed) to experiment with adding immutable RNGs to Random, but it’s a pretty big refactor to integrate them properly and there would be some debate over the interface.