Skip to content

Improve polygonize performance: single-pass tracing, JIT merge helpers, batch shapely#1010

Merged
brendancol merged 3 commits into
masterfrom
issue-1008
Mar 16, 2026
Merged

Improve polygonize performance: single-pass tracing, JIT merge helpers, batch shapely#1010
brendancol merged 3 commits into
masterfrom
issue-1008

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

@brendancol brendancol commented Mar 15, 2026

Summary

Two internal performance changes to polygonize. No API changes.

_simplify_ring, _signed_ring_area, and _point_in_ring were plain Python loops in the dask chunk-merge path. Added @ngjit so they compile with numba.

_to_geopandas now batch-constructs hole-free polygons via shapely.linearrings() + shapely.polygons() on shapely 2.0+. Polygons with holes and older shapely fall back to the scalar constructor.

(An earlier commit tried replacing the two-pass _follow with a single-pass buffer-growth approach. Benchmarks showed it was 15-30% slower -- numba already optimizes the two-pass loop well, and the buffer growth check added inner-loop overhead. Reverted.)

Benchmarks

Benchmark Master PR Change
Tracing (unchanged)
numpy 300x150 5.10 ms 5.19 ms ~same
numpy 1000x500 79.47 ms 80.36 ms ~same
JIT merge helpers
_point_in_ring 10k pts 5.61 ms 0.01 ms 561x
_simplify_ring 5k pts 1.51 ms 0.01 ms 151x
Dask end-to-end
300x150, chunks=(30,30) 1198 ms 471 ms 2.5x
300x150, chunks=(15,15) 2592 ms 1121 ms 2.3x
1000x500, chunks=(50,50) 89.7 s 36.3 s 2.5x
Geopandas output
300x150 127 ms 78 ms 1.6x
1000x500 1423 ms 970 ms 1.5x

Test plan

  • All 74 existing polygonize tests pass (numpy, dask, cupy, dask+cupy, all output formats)
  • Direct validation of JIT-compiled _simplify_ring, _signed_ring_area, _point_in_ring
  • Checkerboard in small dask chunks forces many boundary merges through JIT path
  • Geopandas output with mixed hole-free and holed polygons through batch shapely path

Closes #1008

…s, batch shapely (#1008)

Replace the two-pass _follow with a single-pass implementation using a
dynamically-grown buffer. This eliminates retracing every polygon
boundary a second time, which was the dominant cost for rasters with
many small regions.

Add @ngjit to _point_in_ring, _simplify_ring, and _signed_ring_area
so the dask chunk-merge path runs compiled instead of interpreted.

Use shapely.polygons() batch constructor for hole-free polygons in
_to_geopandas (shapely 2.0+, with fallback for older versions).
- Buffer growth: snake-shaped polygon with >64 boundary points
- JIT merge helpers: direct tests of _simplify_ring, _signed_ring_area,
  _point_in_ring
- Dask merge: checkerboard pattern forcing many boundary merges
- Geopandas batch: mixed hole-free and holed polygons through the
  shapely.polygons() batch path
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Mar 15, 2026
…1008)

Benchmarks showed the single-pass _follow was 15-30% slower than the
original two-pass version. The buffer growth check in the inner loop
adds overhead that numba doesn't optimize away, and the two-pass
approach benefits from the data being in cache on the second pass.

Reverted _follow to the original two-pass implementation. The JIT
merge helpers (2.3-2.6x dask speedup) and batch shapely construction
(1.3-1.6x geopandas speedup) are kept.
@brendancol brendancol merged commit 907a3cd into master Mar 16, 2026
11 checks passed
@brendancol brendancol deleted the issue-1008 branch May 4, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve polygonize performance: single-pass tracing, JIT merge helpers, batch shapely

1 participant