Skip to content

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

Open
ben-milanko wants to merge 2 commits into
fleaflet:masterfrom
ben-milanko:perf/quadratic-hole-points
Open

perf: avoid quadratic hole-point iteration in offset generation & triangulation#2211
ben-milanko wants to merge 2 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)
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