Skip to content

r.sim: Replace custom chunk computation with OpenMP for loop - #7995

Merged
wenzeslaus merged 1 commit into
OSGeo:mainfrom
wenzeslaus:simwe-walker-omp-for
Oct 2, 2026
Merged

wenzeslaus merged 1 commit into
OSGeo:mainfrom
wenzeslaus:simwe-walker-omp-for

Conversation

@wenzeslaus

@wenzeslaus wenzeslaus commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

The walker loop in r.sim currently computes each thread's range of walkers by hand, which is more code than OpenMP's own worksharing loop, which does basically the same. This replaces the hand-computed ranges with #pragma omp for schedule(static). Single-threaded results do not change.

Details (AI generated)
  • Change: in main_loop() (raster/r.sim/simlib/hydro.c), the #if defined(_OPENMP) block that computed min_loop and max_loop from omp_get_thread_num() and omp_get_num_threads() is replaced by #pragma omp for schedule(static) on the plain loop over all walkers (2 lines added, 13 removed).
  • Why: the hand-computed ranges once dropped walkers through a rounding error (fixed in "Simulate all walkers when running in parallel", r.sim: Simulate all walkers when running in parallel #7964). They are also not a worksharing loop, so code added after the loop in the same parallel region would need an explicit barrier to see all walkers done. The omp for ends with an implicit barrier.
  • Kept as is: the enclosing #pragma omp parallel region stays, rather than being merged with the loop into parallel for, so that the loop body keeps its indentation and the diff stays small. lw is dropped from firstprivate, since the loop variable of a worksharing loop is private.
  • What differs: how the walkers are split among threads. The hand-computed ranges give each thread ceil(nwalk / nthreads) walkers until they run out, so the last threads get fewer or none: 10 walkers on 4 threads are split 3, 3, 3, 1, and 2205 are split 552, 552, 552, 549. schedule(static) spreads the remainder instead (with GCC's libgomp: 3, 3, 2, 2 and 552, 551, 551, 551); the OpenMP standard only requires approximately equal contiguous chunks, at most one per thread, so the exact split is up to the implementation. Each thread still gets one contiguous block of walkers, and with one thread the order is unchanged. With the shared random number generator, multi-threaded results depend on scheduling anyway.
  • Output: single-threaded depth and discharge from the nc_spm elevation (200x200 cells, 200k walkers, 10 min) are byte-identical to main with infiltration 0 and 20, with and without diffusion. The r.sim.water and r.sim.sediment gunittest suites and all r.sim pytest tests pass, including the test that all walkers are simulated with 4 threads.
  • Timing: within noise. Three interleaved runs of over 10 minutes each (200x200 cells, 4M walkers, 70 min simulated, no infiltration; the machine was shared, load 2.5 to 11.5), median (range):
nprocs main this PR
1 600 s (595–608) 596 s (591–612), −0.7%
8 1294 s (1275–1317) 1297 s (1293–1367), +0.3%

Squash-merge commit message

Title:

r.sim: Replace custom chunk computation with OpenMP for loop

Body:

The walker loop computed each thread's range of walkers by hand from
the thread number and count. A worksharing loop with static schedule
does the same with one pragma, and ends with an implicit barrier, so
code after the loop in the same parallel region sees all walkers
processed without an explicit barrier. The loop variable is private in
a worksharing loop, so it is no longer listed as firstprivate.

Single-threaded results do not change: the depth and discharge maps
computed from the nc_spm elevation, with and without infiltration and
diffusion, are byte-identical to those before this change, and the
run time is the same within noise.

Written with the help of Claude Code.

The change was suggested by and done with Claude Code (Opus); reviewed by me.

The walker loop computed each thread's range of walkers by hand from
the thread number and count. A worksharing loop with static schedule
does the same with one pragma, and ends with an implicit barrier, so
code after the loop in the same parallel region sees all walkers
processed without an explicit barrier. The loop variable is private in
a worksharing loop, so it is no longer listed as firstprivate.

The parallel region is kept, rather than merged with the loop into a
parallel for, to keep the loop body at its indentation.

Single-threaded results do not change: the depth and discharge maps
computed from the nc_spm elevation, with and without infiltration and
diffusion, are byte-identical to those before this change, and the
run time is the same within noise at 1, 4 and 8 threads.

Written with the help of Claude Code.
@wenzeslaus
wenzeslaus enabled auto-merge (squash) October 1, 2026 20:51
@github-actions github-actions Bot added raster Related to raster data processing C Related code is in C module labels Oct 2, 2026
@wenzeslaus
wenzeslaus merged commit 2e636fb into OSGeo:main Oct 2, 2026
25 of 26 checks passed
@wenzeslaus
wenzeslaus deleted the simwe-walker-omp-for branch October 2, 2026 01:20
@github-actions github-actions Bot added this to the 8.6.0 milestone Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C Related code is in C module raster Related to raster data processing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants