Skip to content

perf(engine): implement two-tier browse-resistant MLD path unpacking cache#7540

Open
DennisOSRM wants to merge 6 commits into
masterfrom
two_tier_cache
Open

perf(engine): implement two-tier browse-resistant MLD path unpacking cache#7540
DennisOSRM wants to merge 6 commits into
masterfrom
two_tier_cache

Conversation

@DennisOSRM
Copy link
Copy Markdown
Collaborator

Summary

Implements the path unpacking cache proposed in #3835.

During MLD route computation, the recursive search() function repeatedly unpacks the same sub-paths between partition-cell border nodes at lower hierarchy levels. This PR caches those results so that identical (source, target, level, cell_id) lookups are served from memory instead of triggering a full sub-search.


Implementation

BrowseResistantCache<K, V, CostFn> (include/util/browse_resistant_cache.hpp)

A new general-purpose two-tier LRU cache resistant to sequential-scan ("browse") pollution:

  • L1 – probationary tier: new entries land here.
  • L2 – protected tier: a second cache hit promotes an entry from L1 to L2.
  • Demotion on L2 eviction: entries evicted from L2 fall back to L1 rather than being discarded outright, so heavily-used sub-paths survive one-shot traversals.

A simple single-tier LRU was evaluated during development but showed no measurable performance advantage over the two-tier design. The two-tier approach was kept for its better resilience against pathological sequential-access patterns.

MLD unpacking cache (include/engine/search_engine_data.hpp)

  • Cache key: (source, target, level, cell_id)MLDUnpackingCacheKey
  • Cache value: unpacked nodes + edges vectors — MLDUnpackingCacheValue
  • Allocated per thread as thread_local UnpackingCachePtr unpacking_cache inside SearchEngineData<MLD>
  • Budget: 10% of estimated graph memory (split 20% L1 / 80% L2), computed from node/edge counts at initialization

Integration (include/engine/routing_algorithms/routing_base_mld.hpp)

Cache lookup and insertion are guarded by if constexpr (requires { engine_working_data.unpacking_cache; }), so algorithms that do not carry a cache incur zero overhead.


Benchmark results

Measured on an M4 Pro CPU, comparing a hot-cache run against master (no cache). Times in milliseconds:

Min Max Average Median
Master 0.21 8.95 3.13 3.12
Cache 0.22 7.76 2.46 2.48
Speedup 1.15× 1.27× 1.26×

Additional changes in this PR

  • car profile: add priority_penalty (0.7×) applied to the non-priority direction on roads tagged with priority=forward or priority=backward
  • way_handlers: apply priority_penalty in the penalties handler; register the tags in taginfo.json
  • bicycle profile: fix the bike-push handler so foot_forward/foot_backward overrides are applied regardless of way_type_allows_pushing

Notes

The implementation was written by the author. Only the commit message and this PR description were produced with AI assistance.

…cache

Closes #3835

Add BrowseResistantCache<K,V> (include/util/browse_resistant_cache.hpp), a
two-tier LRU designed to resist sequential-scan ("browse") pollution.
New entries are admitted to a probationary tier (L1); a second hit promotes
them to a protected tier (L2). L2 evictions demote to L1 rather than being
discarded outright, so frequently-used sub-paths survive one-shot sequential
traversals.

A simple single-tier LRU was evaluated during development but showed no
measurable performance advantage over the two-tier design, which also provides
better robustness against pathological access patterns.

The cache is keyed on (source, target, level, cell_id) and stores the unpacked
node and edge sequences for each sub-path resolved during MLD path expansion.
It is allocated per thread in SearchEngineData<MLD> and sized at 10% of the
estimated graph memory (20% L1 / 80% L2), keeping memory overhead proportional
to the graph being served.

Integration in routing_base_mld.hpp uses if constexpr to detect cache
availability, keeping the path branchless at compile time for algorithms that
do not carry a cache.

Benchmark results on an M4 Pro (hot cache vs. no cache, times in ms):

| Metric  | Min  | Max  | Average | Median |
|---------|------|------|---------|--------|
| Master  | 0.21 | 8.95 | 3.13    | 3.12   |
| Cache   | 0.22 | 7.76 | 2.46    | 2.48   |
| Speedup | —    | 1.15 | 1.27×   | 1.26×  |

Also included:
- car profile: add priority_penalty (0.7×) for the non-priority direction on
  roads tagged with priority=forward/backward
- way_handlers: apply priority penalty in penalties handler; update taginfo.json
- bicycle profile: fix bike-push handler so foot_forward/foot_backward overrides
  are applied regardless of way_type_allows_pushing

Note: the implementation was written by the author; only the commit message and
PR description were produced with AI assistance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 7, 2026 18:26

This comment was marked as outdated.

DennisOSRM and others added 5 commits May 7, 2026 20:43
- browse_resistant_cache: fix insert() to remove duplicate keys from L1/L2
  before inserting, preventing orphaned list nodes and counter corruption
- browse_resistant_cache: fix get() to re-lookup the key in both tiers after
  promote()+evict_l2(), guarding against the entry being demoted back to L1
  when its cost exceeds the L2 budget
- routing_base_mld: guard unpacking_cache dereference with a null check so
  call paths that skip InitializeUnpackingCache() don't segfault
- search_engine_data: track last-seen node/edge counts in the cache's thread-
  local state; recreate and clear the cache when counts change (e.g., on
  data reload) instead of blindly reusing a stale budget
- profiles/car.lua: fix priority_penalty comment (not narrow-road-specific)
- features/car/priority.feature: fix description and scenario name to reflect
  that the penalty applies to the non-priority direction on oneways too
- unit_tests: add duplicate-key insert tests for both L1 and L2 tiers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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