Why is this line like this in Base.Random?

The Base.Random implementation of per-thread global RNGs seems to be a pattern for thread-safe mutations in Julia. I’m trying to implement something similar for a problem that strongly benefits from caching intermediate results for future calls to use.

However, I don’t understand the reasoning behind the one line in the __init__() function. Here’s the Base.Random.RNGs source from master,

const THREAD_RNGs = MersenneTwister[]
@inline default_rng() = default_rng(Threads.threadid())
@noinline function default_rng(tid::Int)
    0 < tid <= length(THREAD_RNGs) || _rng_length_assert()
    if @inbounds isassigned(THREAD_RNGs, tid)
        @inbounds MT = THREAD_RNGs[tid]
    else
        MT = MersenneTwister()
        @inbounds THREAD_RNGs[tid] = MT
    end
    return MT
end
@noinline _rng_length_assert() =  @assert false "0 < tid <= length(THREAD_RNGs)"

function __init__()
    resize!(empty!(THREAD_RNGs), Threads.nthreads()) # ensures that we didn't save a bad object
end

My naive approach would have been to write an __init__() which creates nthreads() copies of MersenneTwister(). Why is it preferable to fill an array with undef?

1 Like

In my understanding, it’s to reduce load time. Creating a MersennTwister is not free, so creating nthreads() copies of it unconditionally even when they are not needed is a waste of resources. One goal is to make julia startup as fast as possible.

3 Likes

Creating all the RNG on the same thread was measurably slower due to false sharing.

2 Likes

Thank you, that makes a lot of sense.