Skip to content

perf: cache zoom-independent marker projections in MarkerLayer#2213

Open
ben-milanko wants to merge 2 commits into
fleaflet:masterfrom
ben-milanko:perf/marker-projection-cache
Open

perf: cache zoom-independent marker projections in MarkerLayer#2213
ben-milanko wants to merge 2 commits into
fleaflet:masterfrom
ben-milanko:perf/marker-projection-cache

Conversation

@ben-milanko

Copy link
Copy Markdown
Contributor

While profiling flutter_map in a production app on low-powered hardware, I found that MarkerLayer re-projects every marker (projectAtZoom, which involves trigonometry for spherical mercator) on every camera change. The LatLng → projected-space step only depends on the CRS — not on the camera position or zoom — so it can be cached, leaving only the linear projected → screen transform per marker per frame.

This PR converts MarkerLayer to a StatefulWidget that caches the projected coordinates per markers list (re-projecting on any didUpdateWidget, matching the invalidation convention of the polyline/polygon layers, so in-place list mutation behaves exactly as before). It also hoists pixelBounds, pixelOrigin, and the zoom scale out of the per-marker loop. The non-finite-LatLng guard from Crs.latLngToXY (#2178) is preserved at cache-build time.

Results

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

before after
10k markers, mostly culled, panning 1,861 µs/frame 1,522 µs/frame (~18%)
Per-marker projection kernel 47 ns 40 ns
Many visible markers unchanged (dominated by Stack diffing) unchanged

All existing tests pass. No API change — MarkerLayer's constructor and fields are untouched.

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

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)
MarkerLayer re-projected every marker (LatLng -> projected space, which
involves trigonometry for spherical mercator) on every camera change.
The projection only depends on the CRS - not on the camera position or
zoom - so cache it per markers list and apply just the linear
projected -> screen transform per frame. Also hoists pixelBounds,
pixelOrigin, and the zoom scale out of the per-marker loop.

Cache invalidation matches the polyline/polygon layers' convention: any
new widget instance re-projects (didUpdateWidget), so in-place mutation
of the markers list behaves as before. The finiteness guard from
Crs.latLngToXY (fleaflet#2178) is preserved at cache-build time.

Benchmark (benchmark/feature_layer_benchmark_test.dart, JIT):
10k markers, mostly culled, panning: 1861 -> 1522 us/frame (~18%).
Scenarios with many visible markers are dominated by Stack diffing and
unchanged.
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