Skip to content

Apply marker changes to the grid index instead of rebuilding it - #21

Merged
wf9a5m75 merged 1 commit into
developfrom
perf/grid-index-incremental
Nov 5, 2026
Merged

wf9a5m75 merged 1 commit into
developfrom
perf/grid-index-incremental

Conversation

@wf9a5m75

@wf9a5m75 wf9a5m75 commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

#20(K-D 木の削除)の上に積んでいる。#20 をマージしたあと、base を develop に切り替える。

MapConductor/android-sdk-core#20 と同じ変更の iOS 版。

不具合

MarkerGridIndex は、マーカーの追加・削除・更新のたびに捨てられ、次の検索が全件の作り直しを待っていた。パンのたびにマーカーを差し替えるアプリでは、次のタップとすべてのタイル描画がこれを待つ。ドラッグでは毎フレーム待つ。Android(Pixel 5a)では、300 件を差し替えた直後の検索が 14.4 万件で 212 ms だった。

修正

  • 追加:次のスロットを渡し、エントリは未反映の一覧に置く。次の検索で、その分だけを並べ、整列済みの配列と 1 回の走査で合わせる
  • 削除:スロットを空(nil)にするだけ。検索は空きを飛ばし、次の合わせ込みで空きのエントリを落とす
  • 同じセルのままの差し替え(アイコンやフラグの変更):スロットの参照を入れ替えるだけ
  • 作り直すのは、スロットの半分が空いたとき(4,096 以上)と、消すように言われたマーカーが見つからないときだけ
  • マーカーごとの対応表は持たない。消すエントリは、今の位置のセルを二分探索し、同一オブジェクトで照合して見つける。ドラッグのように位置をその場で書き換えてから知らされたものは別のセルにあるので、その時だけスロットを端から探す
  • 間引き検索:空きを飛ばして、セルの最後の生きているものを代表にする。代表が消えたセルも抜けない

MarkerManager は invalidate() の代わりに add / remove / replace を呼ぶ(すべて既存のロックの中)。公開 API の変更は無い(MarkerGridIndex は internal)。

確認

  • xcodebuild build-for-testing -destination 'generic/platform=iOS':通る

  • シミュレータ・実機ではテストを流せていない。手元の CoreSimulator が古く、パッケージのテストはホストアプリが無いので実機では動かない。代わりに MarkerGridIndex.swift・MarkerGrid.swift・GeoPoint.swift・GeoRectBounds.swift をそのまま macOS 向けの SwiftPM パッケージに入れ(MarkerEntity / MarkerState は ID・位置・同一性だけのスタブ)、MarkerGridIndexTests を流した

    • 既存の 10 件と追加した 4 件、計 14 件すべて通る
    • 追加したテストは android-sdk-core#20 と 1 対 1:追加・削除・同じ位置での差し替え・別の位置での差し替え・その場での移動を 1,000 回混ぜても総当たりと一致し、作り直さない/代表が消えたセルを一つ前で埋める/見つからないものを消すと作り直す/半分以上消すと作り直して詰める
    • 実装をわざと壊して(同じセルの差し替えで参照を替えない/空きを飛ばさない/その場で動いたものを探さない/削除で空けない)、それぞれ落ちることを確かめた
  • Mac・リリースビルド、同じハーネス。日本全域に散らばるマーカー、300 件差し替え直後のクリック範囲の検索(10 回の中央値)と、ドラッグ 1 フレーム(移動+検索、30 回の中央値)

    6 万件 前 → 後 14.4 万件 前 → 後
    差し替え直後の検索 5.69 → 0.22 ms 10.55 → 0.54 ms
    ドラッグ 1 フレーム 4.29 → 0.00 ms 10.40 → 0.00 ms

    実機では測っていない。

MarkerGridIndex was thrown away on every add, remove or update, so the first
query after any change paid the full build. An app that swaps markers on
every pan paid it on its next tap and on every tile, and a drag paid it on
every frame. On Android's Pixel 5a, after 300 swaps, that was 212 ms at
144,000 markers.

A marker that arrives now takes the next slot, and its entry waits in a
pending list; the next query sorts only those and merges them into the
sorted array in one pass. A marker that leaves empties its slot, which
queries skip and the next merge drops. A replacement that stays in its cell
swaps the reference in its slot and nothing more. The index rebuilds only
when half its slots are empty or when it cannot find a marker it was told
to forget.

Nothing is kept per marker to find its entry: it is in the run of the cell
its position falls in, matched by identity. A marker moved in place before
it is reported, as a drag does, has its entry in another cell's run; for
that one the slots are scanned.

The thinned query took a cell's last entry; with emptied slots it steps back
to the last live one, so a cell whose representative left keeps one.

Same change as MapConductor/android-sdk-core#20.
Base automatically changed from chore/remove-kdtree to develop November 5, 2026 09:07
@wf9a5m75
wf9a5m75 merged commit 34cfc38 into develop Nov 5, 2026
2 checks passed
@wf9a5m75
wf9a5m75 deleted the perf/grid-index-incremental branch November 5, 2026 09:08
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