Global mutable Rng

I need to create a global mutable random number generator. This is for efficiency purposes as thread_rng() is too slow to call, and it's returning the type I want. (I'm aware of the safety implications of having global singletons; I'm using just a single-thread.)

Ideally I want the SmallRng, which is fast, but it doesn't appear to exist in the create (is this perhaps a docs problem -- there's no version so I couldn't check). For now I'll be happy with a XorShiftRng.

Something like:

static mut global_rng : XorShiftRng = ??

I know you can't call functions here so I instead tried wrapping it in an Option and function:

static mut global_rng : Option<XorShiftRng> = None;
fn get_global_rng() -> &'static mut XorShiftRng {
}

But I can't figure out how to return and/or initialize the value in this function.

How do you want to synchronize concurrent access by multiple threads?

1 Like

What prevents you from keeping the generator ref that you get out of thread_rng() around for longer?

(Full disclosure: I take global mutable RNGs out of other people's code for a living. And it is no fun.)

1 Like

I'm not using multiple threads in this code. I don't need to synchronize access to to the generator.

The place in the code that needs the generator is deep within the tree and not logically related to the high-level functions that invoke it. Adding a context, or passing the generator, down through the call-tree would be prohibitive and result in unclean code.

Is the thread-localness the performance problem with rand::thread_rng or the choice of algorithm? If it's the second, the simplest route is to use a thread local of your own rather than a static.

1 Like

Both in this case. The lookup time for the tread-local is high, as well as the algorithm being slow. I've timed both of them. The lookup is more costly, but the algorithm itself is also higher than desired. In the application I only need rough pseudo-randomness to apply a monte-carlo algorithm. My fallback would thus be a few static mut variables to create a dumb algorithm. I was hoping instead to make use of XorShift, or the SmallRng, instead.

I was also hoping to understand how to create such global mutable values, as they do come up in other contexts as well.

With the caveat that this will explode horribly if multiple threads touch it, this should do what you want: Rust Playground

Once it's possible to define const functions in stable Rust, you can get rid of the Option dance.

5 Likes

Cute trick for static initialization!

Wouldn't lazy_static do the same thing? Admittedly it's not hard to do manually, but I assume that using lazy_static would have the same efficiency.

Lazy static doesn't allow mutation of the inner value without synchronization or UnsafeCell.

2 Likes

You can also one-line @sfackler's solution:

RNG.get_or_insert_with(XorShiftRng::new_unseeded)
4 Likes

I use random numbers quite a bit in parallel simulations that I'd like to be repeatable (seedable) regardless of threading, so for anyone that does need thread safety and doesn't want to pass around a RNG reference, here's an example of what I do:

#![feature(thread_local)]

extern crate rand;
extern crate xorshift;

use rand::distributions::normal::StandardNormal;
use xorshift::{Rand,Rng,SeedableRng,SplitMix64,Xoroshiro128};

#[thread_local]
static mut RNG:Option<Xoroshiro128> = None;

pub fn seed(x:u64) {
    let mut seeding_rng:SplitMix64 = SeedableRng::from_seed(x);
    unsafe { RNG = Some(Rand::rand(&mut seeding_rng)); }
}

pub fn rnorm() -> f64 {
    unsafe {
        let StandardNormal(x) = RNG.as_mut().unwrap().gen::<StandardNormal>();
        x
    }
}

pub fn rnormv<R:DimName,C:DimName>() -> MatrixMN<f64,R,C>
    where DefaultAllocator:Allocator<f64,R,C> {
    unsafe { MatrixMN::<f64,R,C>::from_iterator(RNG.as_mut().unwrap().gen_iter().map(|x| { let StandardNormal(y) = x; y })) }
}
1 Like

Could you try out with the current git version of rand? Performance of ThreadRng has improved a lot, it is now at 40~50% of XorShiftRng.

I'm limited to fixed versions on the target platform. I'm likely to stop using XorShiftRng as well since it's also slower than desired. I'll probably fall back to a trivial pseudo-random algorithm -- which is all that is needed in this case.

Here's the PRNG I use for my single-threaded raytracer WIP. NSFW if you are a diehard Rust programmer :slight_smile:. Fast, though.

//! Terrible LCG PRNG because Rust and global state.
//! http://nuclear.llnl.gov/CNP/rng/rngman/node4.html

/// Produce a pseudo-random integer.
pub fn random() -> u64 {
    unsafe {
        static mut STATE: u64 = 0x123456789abcdef0u64;
        let tmp =
          STATE.wrapping_mul(2862933555777941757u64) + 3037000493u64;
        STATE = tmp;
        tmp
    }
}
3 Likes

Why wrapping_mul, but no wrapping_add?

Just forgotten, I think. I don't recall any better reason. Honestly, that whole thing is pretty bad. Maybe more like

pub fn random() -> u64 {
    unsafe {
        static mut STATE: u64 = 0x123456789abcdef0;
        STATE = STATE.wrapping_mul(2862933555777941757)
            .wrapping_add(3037000493);
        STATE
    }
}