[GRASS-git] [OSGeo/grass] c83afe: lib: Make random number generation thread safe (#6...
Māris Nartišs
noreply at github.com
Thu Sep 10 14:15:55 PDT 2026
Branch: refs/heads/main
Home: https://github.com/OSGeo/grass
Commit: c83afef6d32e3747538190aa95ce2d23c2cb025e
https://github.com/OSGeo/grass/commit/c83afef6d32e3747538190aa95ce2d23c2cb025e
Author: Māris Nartišs <maris.gis at gmail.com>
Date: 2026-09-10 (Thu, 10 Sep 2026)
Changed paths:
M lib/gis/lrand48.c
A lib/gis/testsuite/test_lrand48.py
Log Message:
-----------
lib: Make random number generation thread safe (#6480)
G_lrand48(), G_mrand48(), and G_drand48() advance a shared generator
state kept in plain static variables, so concurrent calls from multiple
threads race on it, which is undefined behavior and was observed in
r.mapcalc after thread support was merged (#5742).
Keep the whole 48-bit drand48 state in a single atomic integer and
advance it with a compare-and-swap when C11 atomics are available. A
successful swap is exactly one generator step, so for a given seed the
threads together consume exactly the single-threaded sequence, and
single-threaded output is unchanged. Which thread receives which value
still depends on scheduling, so full reproducibility continues to
require single-threaded execution. Without C11 atomics (notably MSVC,
which defines __STDC_NO_ATOMICS__), the previous non-atomic code is
kept and multi-threaded use remains unsafe. Seeding is not made
thread-safe; seed once before starting worker threads.
A new test tests that draws from four threads concurrently and checks
that the combined draws are exactly the single-threaded sequence.
Advancing one shared atomic generator state makes every draw a
lock-prefixed compare-and-swap on a single cache line. For callers that
draw heavily inside a parallel region, that costs more than the race it
removes.
Measured with r.sim.water on a 200x200 region, 200k walkers, 36 cores.
Three repetitions per configuration, warm-up discarded, configurations
interleaved, medians reported. Like for like at each thread count, this
PR is 13% faster at nprocs=1, where the 64-bit linear congruential
generator step beats the old 16-bit limb arithmetic, and 55% slower at
4 and 8 threads, 50% slower at 16. r.sim spends at least two draws per
walker per iteration in the gasdev_for_paralel rejection loop, so it is
close to a worst case among in-tree callers; code that draws less often
should see much less.
This needs to be changed even with the slow-down.
The faster baseline is racy and its behavior is undefined.
Co-authored-by: Vaclav Petras <wenzeslaus at gmail.com>
To unsubscribe from these emails, change your notification settings at https://github.com/OSGeo/grass/settings/notifications
More information about the grass-commit
mailing list