Skip to content

perf: cull polylines via cached projected bounding boxes#2212

Open
ben-milanko wants to merge 2 commits into
fleaflet:masterfrom
ben-milanko:perf/polyline-culling-bbox
Open

perf: cull polylines via cached projected bounding boxes#2212
ben-milanko wants to merge 2 commits into
fleaflet:masterfrom
ben-milanko:perf/polyline-culling-bbox

Conversation

@ben-milanko

Copy link
Copy Markdown
Contributor

While profiling flutter_map in a production app on low-powered hardware, I found two per-frame O(points) scans in PolylineLayer's aggressive culling:

  • stretchesBeyondTheLimits() iterates every point of every polyline that overlaps the viewport latitudes, every frame
  • fully-visible polylines still go through the per-segment aabbContainsLine scan (and sublist allocations), even though nothing needs to be culled

This PR caches the projected-space bounding box on _ProjectedPolyline (computed lazily — culled fragments never compute one; cached instances are created once per zoom level alongside simplification). The world-stretch check becomes an O(1) bbox comparison, and polylines whose bbox is contained in the viewport bounds are yielded whole, skipping the segment scan entirely.

Results

Measured with flutter test benchmark/feature_layer_benchmark_test.dart (JIT, best-of-reps):

before after
600 polylines × 60 pts, all visible, panning 2,631 µs/frame 2,484 µs/frame (~6%)
Mostly-culled scenario unchanged unchanged

Modest in this paint-dominated scenario, but the cull step itself no longer scales with point count, which matters as polyline density grows.

All existing tests pass. The benchmark harness commit is shared with #2211 (identical file content — it collapses out of this diff once either merges).

Cache the projected-space bounding box on each projected polyline
(lazily, so culled fragments never compute one). This replaces two
per-frame O(points) scans in aggressive culling:

- world-stretch detection now compares the bbox against the world east
  and west edges, instead of iterating every point of every polyline
  that overlaps the viewport latitudes
- fully-visible polylines (bbox contained in the viewport bounds) skip
  the per-segment aabbContainsLine scan and its sublist allocations

Benchmark (benchmark/feature_layer_benchmark_test.dart, JIT):
600 polylines x 60 pts, all visible, panning:
2631 -> 2484 us/frame. Mostly-culled scenario unchanged.
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).

(cherry picked from commit ed76dc6)
@JaffaKetchup

Copy link
Copy Markdown
Member

Hey, thanks for these performance improvements! (Slightly annoyed that I left gaps in when putting all the caching stuff together lol 😂).

I'm still travelling right now, but I'll try to give these a review when I'm back in a couple weeks :)

@ben-milanko

Copy link
Copy Markdown
Contributor Author

Thanks @JaffaKetchup . Full disclosure these came up when I was doing some AI debugging and optimisations.

To provide some verification of the claims, I put together a repo with samples

There's pretty minimal improvements on this optimisation, as seen below.

polylines_before_after.mp4

Full repo is https://github.com/ben-milanko/flutter_map_perf_demo

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