Parallel jobs
Assign a stream to each logical job, then let Julia schedule jobs freely. This avoids shared mutable state and dependence on thread IDs.
using PureRNGs, Random
using Base.Threads
function job_mean(root, job)
rng = subrng(root, job)
buffer = Vector{Float64}(undef, 1024)
_, rng = randn_next!(rng, buffer)
return sum(buffer) / length(buffer)
end
root = Threefry4x32(123456)
results = zeros(8)
Threads.@threads for job in eachindex(results)
results[job] = job_mean(root, job)
end
serial = [job_mean(root, job) for job in eachindex(results)]
@assert results == serial
results8-element Vector{Float64}:
0.0374112407193757
0.01987941592531343
0.037528078321304205
-0.017334466673338045
-0.015131199757773167
-0.053737740582231894
0.008660029882361716
-0.01941645601650538The root uses a 128-bit key to leave room for many derived job keys. Inner fills run serially by default, so the outer loop owns all the parallelism.
subrng reduces its purpose ID modulo 2^64, so job IDs differing by 2^64 alias.
If a job needs several batches, continue its local generator with randn_next!. Do not derive the same job key again for each batch unless repetition is intentional.
Changing the thread count preserves each job's input stream. Changing a floating-point reduction order can still change the final computed statistic.
Derive from the job index, not from a running generator
Deriving inside a loop that also advances the parent gives the same job stream every iteration, because derivation ignores the parent's position:
function repeated_jobs(rng, n)
means = Float64[]
for _ in 1:n
child, _ = splitrng(rng)
values, child = rand_next(child, Float64, 16)
push!(means, sum(values) / 16)
_, rng = rand_next(rng, Float64)
end
return means
end
repeated_jobs(Threefry4x32(123456), 3)3-element Vector{Float64}:
0.40514515954195146
0.40514515954195146
0.40514515954195146The loop index is the purpose ID, so derive from it directly:
function indexed_jobs(root, n)
return [sum(first(rand_next(subrng(root, i), Float64, 16))) / 16 for i in 1:n]
end
job_means = indexed_jobs(Threefry4x32(123456), 3)
@assert allunique(job_means)
job_means3-element Vector{Float64}:
0.5396714204793843
0.44995352316336
0.4180330611008525