GRASS GSoC 2026 Parallelizing r.proj and Raster Processing Modules in GRASS

From GRASS-Wiki
Jump to navigation Jump to search
Student Name Kaushik Raja
Organization NumFOCUS
Mentor Name Huidae Cho, Anna Petrasova, Vaclav Petras
GitHub Fork View Repo
LinkedIn Profile View LinkedIn

Abstract

R.proj, r.param.scale, and r.geomorphon are some of the most commonly used modules in GRASS. R.proj reprojects raster maps between coordinate systems. This is important because real world data comes in many different projections, and maps have to be in the same one before they can be analyzed together. So many GRASS workflows that combine data sources start with r.proj. R.param.scale calculates terrain parameters like slope and curvature by sliding a window over an elevation map. R.geomorphon classifies every cell of an elevation map into a landform like a ridge, valley, or peak. It does this by looking outward from each cell in eight different directions and checks whether the terrain rises above or drops below the line of sight. That pattern of visible horizons tells it what shape the land around the cell is.

The problem with all three modules is that they have always been single threaded. On modern hardware most CPU cores sit idle while one core does all the work, so large maps take much longer time to work and return an output. The goal of this project is to parallelize them with OpenMP so that many threads work on the map at the same time.

The main challenge with parallelizing these modules is memory. To help manage memory and get the most speedup ratios possible, each module will work on a chunk of rows at a time, called a band. The band is sized so that the input it needs stays under a memory limit the user controls. The threads then split the rows of the band among themselves and work on them at the same time. In r.proj, each thread also gets its own file descriptor and its own PROJ object (for thread safety). Some projections bend so much that even one full row’s width is too much input to fit under the memory limit, so those are read in smaller column pieces instead. In r.param.scale the old sliding window was replaced with this same band design, and in between I found and fixed a data race in the GRASS math library.

As a result of this, users can use one of the three modules and get their outputs faster than before. Also, the band pattern used in these modules gives future contributors a template for parallelizing other raster modules.

Project Scope

  1. Parallelize r.param.scale with OpenMP under a user controlled memory limit
  2. Parallelize r.proj
  3. Parallelize r.geomorphon
  4. Add nprocs option so user can say how many threads form their machine they want to use
  5. Find and fix data races in the GRASS libraries that block modules from being parallelized
  6. Replace the old testsuites for r.param.scale, r.proj, and r.geomorphon with pytest tests
  7. Verify every parallel module produces output identical to the serial version
  8. Add a benchmarking script for each module and benchmark all modules across thread counts

Timeline

Period Timeline Tasks Status
Community Bonding Period May 1 - May 25
  1. Thread safety audit of gprojects library calls
  2. Study how the code in readcell.c works
  3. Set up benchmarking infrastructure and figure out how that works
  4. Look at remaining modules and understand how they work
  5. Finalize dev environment
  6. Confirm benchmarks are reproducible
  7. Agree on implementation details with mentors
Done
Official Coding Period May 25 - June 8
  1. Build r.proj proof of concept with RAM buffer and per thread PROJ contexts (PR #7185)
  2. Benchmark RAM buffer approach against tile cache approach
  3. Add user controlled memory option
  4. Build first r.param.scale parallel draft (PR #7236)
  5. Rule out a suspected Mac OpenMP bug, traced to benchmark setup
Done
June 9 - June 22
  1. Redesign r.param.scale from a single strip into a two level band and chunk structure that honors the memory option, modeled on r.neighbors (PR #7440)
  2. Give each thread its own input access so threads no longer share one reader
  3. Measure 4.6x speedup at 8 threads, up from 2.7x before the redesign, with output identical to the serial module
  4. Trace a scaling dip to thread load imbalance using per thread timing, correlation of 0.99 between imbalance and slow runs, ruling out a CPU frequency explanation
  5. Find a data race in G_ludcmp in the GRASS math library affecting every parallel caller, prove it with ThreadSanitizer and a controlled toggle experiment, report it (issue #7539)
  6. Fix the race and verify with ThreadSanitizer showing 14 race reports before and 0 after, repeated test runs going from 10 failures in 100 to 0 in 20, and a 40 configuration bit identical output comparison
  7. Check every caller of the fixed function at runtime to confirm safety for dependent modules
  8. Debug CI failures across platforms including CMake PROJ linkage and unguarded OpenMP timer calls, resolve a git branch divergence, unskip and pass the pytest suites on Python 3.10 and 3.13, pass all 26 checks, mark PR #7440 ready for review
Done
June 23 - July 6
  1. Design and build the band based r.proj (PR #7627)
  2. Size each band by projecting its edges back to the input to find the rows it needs
  3. Cut peak memory from 763 MB to 130 MB with identical output
  4. Measure 2.9x total and 5.2x compute speedup at 8 threads on a 105 million cell map
  5. Answer two rounds of mentor review
Done
July 7 - July 11
  1. Submit midterm evaluation
  2. Fix two CI failures, a missing PROJ dependency in the CMake build and OpenMP timer calls breaking the no OpenMP build
  3. Parallelize input reading with per thread file descriptors, 1.9x faster reads, about 3.0x total
  4. Move per thread PROJ context handling into the gproj library as new API functions at maintainer request
  5. Measure which projections need column splitting with a standalone footprint tool
  6. Split bands into column sections so tilted reprojections that failed under the memory cap now complete, verified identical on three projections
  7. Cut the section sizing search from 54 seconds to 2.6 seconds
  8. Benchmark everything and present results to mentors
Done
July 20 - August 3
  1. Implement the two speedup items from review on PR #7627, keeping the input strip resident across bands and writing the previous band's output while the next band computes
  2. Run bilinear, bicubic and lanczos through the banded parallel path
  3. Move the method reference tests into their own PR #7766, replacing the old testsuite with pytest
  4. Fix a data race on two globals in the projection library (PR #7764, merged)
  5. Run the full benchmark grid, post results and scaling graphs, mark PR #7627 ready for review
  6. Look at r.geomorphon and fix an already existing crash on regions smaller than the search window (PR #7773, merged)
  7. Parallelize r.geomorphon with per band strips, per thread file descriptors and a nprocs option (PR #7783), about 5.1x at 8 threads
Done
August 4 - August 11
  1. Rework the r.proj band sizing after review feedback that the search approach was hard to follow, replacing it with a footprint grid computed once when the user runs the module
  2. Fix a problem related to low memory slowdown on strongly curved projections and fix it by choosing band height and tile width together from the grid
  3. Verify that the output is correct across all methods, thread counts and memory settings, run the full benchmark matrix, open draft PR #7807
  4. Rebuild the r.geomorphon pytest suite on a DEM that produces all ten landform classes and address review (PR #7785)
  5. Address review on PR #7783
Done
August 12 - August 18
  1. Address remaining review on PR #7785, merged
  2. Rebase PR #7783 over main and add the parallel identity tests
  3. Address any review or questions on #7807 and #7766
  4. Update the wiki page with the final report
In Progress
Final Week August 19 - August 26
  1. Finish the final report and share it with mentors
  2. Submit the final work product and final evaluation
  3. Respond to review on the open PRs
In Progress

Reports

Community Bonding Period

During this period I:

  1. Started off with an introductory call with my mentors.
  2. I introduced myself to the community on the OSGeo discourse.
  3. I started this wiki page, created the abstract, project scope, and the timeline.
  4. Did a thread safety audit of the gproj library calls
  5. Studied how the code in readcell.c works
  6. Set up the benchmarking infrastructure and figured out how it works
  7. Looked at the remaining modules and understood how they work
  8. Finalized my dev environment
  9. Confirmed my benchmarks are reproducible
  10. Decided to parallelize r.param.scale first, r.proj second, and then finally r.geomorphon
  11. Worked on identifying methods to parallelize r.param.scale

Week 1

Posted what I did in week 1 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-1-report/153925

Week 2

Posted what I did in week 2 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-2-report/154036

Week 3

Posted what I did in week 3 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-3-report/154478

Week 4 & Week 5

Posted what I did in weeks 4 and 5 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-4-5-report/154479

Week 6

Posted what I did in week 6 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-6-report/154480

Week 7

Posted what I did in week 7 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-7-report/154481

Week 8

Posted what I did in week 8 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-8-report/154560

Week 9

Posted what I did in week 9 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-9-report/154659

Week 10

Posted what I did in week 10 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-10-report/154761

Week 11

Posted what I did in week 11 in the OSGeo discourse: https://discourse.osgeo.org/t/gsoc-coding-period-week-11-report/154833

Week 12

Log of Pull Requests

Pull Request / Issue Description Status
PR #7440 r.param.scale parallelization and G_ludcmp race fix Merged
PR #7764 Fix a data race on two globals in the projection library Merged
PR #7773 Fix a r.geomorphon crash on regions smaller than the search window Merged
PR #7785 Replace the r.geomorphon testsuite with pytest tests Merged
PR #7627 r.proj parallelization with memory bounded bands In review
PR #7807 Simpler r.proj band sizing from a precomputed footprint grid Draft, in review
PR #7783 r.geomorphon parallelization In review
PR #7766 Replace the r.proj method testsuite with pytest tests In review
Issue #7539 G_ludcmp data race report Addressed within #7440
PR #7185 r.proj proof of concept Closed in favor of #7627
PR #7236 r.param.scale proof of concept Closed in favor of #7440