Skip to content

perf: avoid quadratic hole-point iteration in PolygonLayer offset generation & triangulation#2211

Merged
JaffaKetchup merged 3 commits into
fleaflet:masterfrom
ben-milanko:perf/quadratic-hole-points
Jun 30, 2026
Merged

perf: avoid quadratic hole-point iteration in PolygonLayer offset generation & triangulation#2211
JaffaKetchup merged 3 commits into
fleaflet:masterfrom
ben-milanko:perf/quadratic-hole-points

Conversation

@ben-milanko

@ben-milanko ben-milanko commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

While profiling flutter_map in a production app on low-powered hardware, I found that OffsetHelper.getOffsetsXY and the PolygonLayer Earcut coordinate flattening index a lazily concatenated iterable (points.followedBy(holePoints.expand(...))) with elementAt(i). On a lazy iterable that walks from the start on every call, so both paths are O(n²) in the total point count — every frame for any polygon with holes.

This PR flattens the points + hole points into a fixed-length list before the loops (and writes the Earcut input into a Float64List directly).

Results

Measured with the benchmark harness added in the first commit (flutter test benchmark/feature_layer_benchmark_test.dart, JIT, best-of-reps):

before after
getOffsetsXY, 500-point polygon + 10 holes × 200 points 26,761 µs/call 33 µs/call (~800×)

All existing tests pass. No behavioural change intended — output offsets are identical.

The harness commit is shared with #2212 and #2213 (identical file content, so whichever PR merges first, it collapses out of the others' diffs). It lives in benchmark/ rather than test/ so it doesn't extend CI runtime; numbers are only meaningful relative to each other on the same machine.

Widget- and kernel-level CPU benchmarks for the polyline, polygon, and
marker layers, plus a direct getOffsetsXY benchmark. Lives in
benchmark/ (not test/) so it is opt-in and does not extend CI runtime:

    flutter test benchmark/feature_layer_benchmark_test.dart

Numbers are JIT and only meaningful relative to each other (before vs
after a change on the same machine).
…angulation

points.followedBy(holePoints.expand(...)) indexed with per-element
elementAt() walks the lazy iterable from the start on every call, making
OffsetHelper.getOffsetsXY and the Earcut coordinate flattening O(n^2) in
the total point count - on every frame. Flatten into a fixed-length list
instead.

Benchmark (benchmark/feature_layer_benchmark_test.dart, JIT):
getOffsetsXY, 500-point polygon + 10 holes x 200 points:
26,761 us/call -> 33 us/call (~800x)
@ben-milanko

Copy link
Copy Markdown
Contributor Author

@JaffaKetchup here's the demonstration for this change (I know the vid doesn't look great, I think the frame rate of the recording couldn't keep up with the demo)

polygons_before_after.mp4

https://github.com/ben-milanko/flutter_map_perf_demo

@JaffaKetchup JaffaKetchup left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks again!

@JaffaKetchup JaffaKetchup changed the title perf: avoid quadratic hole-point iteration in offset generation & triangulation perf: avoid quadratic hole-point iteration in PolygonLayer offset generation & triangulation Jun 30, 2026
@JaffaKetchup JaffaKetchup merged commit 3d59772 into fleaflet:master Jun 30, 2026
10 of 11 checks passed
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.

2 participants