Skip to content

perf: skip intersects() for rings with disjoint envelopes in correct() - #976

Open
dktue wants to merge 1 commit into
systemed:masterfrom
dktue:perf/correct-combine-boxes
Open

dktue wants to merge 1 commit into
systemed:masterfrom
dktue:perf/correct-combine-boxes

Conversation

@dktue

@dktue dktue commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Gain: Lake Huron, the slowest relation on the planet, 103 s → 25 s (−76%); Germany wall 43.6 → 39.3 s (−10%, three rounds); planet relations phase 310 → 263 s (−15%), with the planet total unchanged within run-to-run noise. Output identical.

When a multipolygon is invalid, tilemaker repairs it with make_valid, i.e. geometry::correct() in include/geometry/correct.hpp. There, result_combine merges each corrected ring into the result by testing it with boost::geometry::intersects() against every ring added before it. That is quadratic in the number of rings, and each test walks both rings.

A few huge water and landcover relations in the planet are self-intersecting and have thousands of inner rings (islands). They dominate the relations phase, because each one runs on a single thread while the others wait. Lake Huron (r1205151, 523 outer and 14,233 inner ways) spends 88 of its 103 s in make_valid, nearly all of it in result_combine.

This PR keeps the envelope of each ring next to it and skips intersects() when the envelopes are disjoint, since rings with disjoint envelopes can't intersect. The rings are merged in the same order as before, so the output is unchanged. It also adds a unit test for correct(), which passes with and without the change.

Slowest planet relations, timed on an extract with just those relations

relation before after change
r1205151 Lake Huron (14,233 inner) 103 s 25 s −76%
r3400721 Guri Reservoir (2,574 inner) 69 s 63 s −9%
r13985343 Lake Nasser (6,758 inner) 54 s 54 s 0%
r20516020 wood (210 inner) 44 s 44 s 0%
r13287625 grassland (1,518 inner) 40 s 30 s −25%

Germany, 32 threads, three rounds each

before after change
wall 43.45 / 43.80 / 43.60 s 39.24 / 39.04 / 39.52 s −4.3 s (−10%)
user CPU 1039 / 1043 / 1042 s 1025 / 1023 / 1029 s −1.5%

Most of the gain is wall time: fewer threads wait at the end of the relations phase for one large relation.

Planet

Sharded --store run, 32 threads (one run each):

before after change
relations phase 310 s 263 s −47 s (−15%)
slowest relations pass (3 of 6) 119 s 79 s −34%
wall 2484 s 2487 s within noise

The relations phase gains 47 s. The total doesn't show it, because the tile phase, which this PR doesn't touch, took 140 s longer in this run; identical planet runs vary by tens of seconds there.

Output

compare_area (per-tile area/length per layer and tag group) finds all layers identical, both for Germany and for the full tile output of the five relations above. On Germany only poi differs, which already differs between identical runs.

Baseline for all measurements: master + #961 + #966 + #968. It changes the same file as #974 (correct.hpp), and the two apply together cleanly.

🤖 Generated with Claude Code

When make_valid repairs a multipolygon, result_combine merges each
corrected ring into the result by testing it against every ring added
before it with boost::geometry::intersects(). That is quadratic in the
number of rings: Lake Huron (r1205151, 14233 inner rings) spends 88 of
its 103 s in it.

Keep the envelope of each ring alongside and skip intersects() when the
envelopes are disjoint, which rules out an intersection. The rings are
merged in the same order, so the output is unchanged. Adds a unit test
for correct().

Lake Huron 103 s -> 25 s (-76%); Germany 43.6 s -> 39.3 s wall (-10%);
planet relations phase 310 s -> 263 s (-15%).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant