Files
arah2/.planning/debug/alt-potential-4x-slowdown.md
T

71 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: resolved_f1_applied
trigger: "ALT triangle-inequality potentials made bidirectional A* ~4x SLOWER on real KL graph (p95 38ms -> 152-185ms); L=32 worse than L=24 — implementation-cost smell, not theoretical wall. Diagnose + propose fix inside potential()/per-search setup only."
created: 2026-10-25T00:00:00Z
updated: 2026-10-25T14:12:00Z
---
## Current Focus
resolution: F1 APPLIED (post-plan remediation session) — cleanColumnsFor per-graph memo + search-scoped activeL (clean columns only); per-node sentinel skip REMOVED; RC-1 closed. Verification: routing 81/81 (A11 re-spec'd RED-first, A13 new mixed-column case), bot 126/126, typechecks clean, new committed oracle sweep 0/200 suboptimal (seed 0x08f1), CPU back in the pre-ALT band (p95 136.6 ms this host vs 1037.6 pre-fix; honest c3 FAIL vs 8 ms stands), D-03 gate green (0 violations). Commits c071e20 / 2a5a0b5 / 97d6e35. F2 (transposed table) remains FUTURE — only relevant when a clean-column binary is rebuilt; ALT is inert (exact haversine) on the current 0/24-clean binary by design.
test: complete — re-specified suite is the standing seal (A9 all-dirty, A10 search-scope skip, A11 F1 structural pins, A12 all-clean ALT-active, A13 subset active list) + measure-astar-oracle-sweep.mjs is the committed fresh-pair optimality pin D-03's fixed corpus structurally cannot be
expecting: nothing — RC-1 resolved; RC-2 escalation (c3) unchanged and owned by the maintainer decision recorded in 08-05-SUMMARY
next_action: none (maintainer may reopen for F2 when a clean-column binary exists)
## Symptoms
expected: ALT bounds dominate haversine bound → fewer expansions → faster or equal CPU (gate p95 budget 8 ms)
actual: p95 152.788 ms (25-route gate), 185.692 ms (100-pair); pre-ALT baseline p95 ≈ 38 ms; L=32 probe WORSE (182–204 ms)
errors: none (correctness green — A1 625-pair equivalence + D-03 optimality gate pass)
reproduction: routing/scripts/measure-route-cpu.mjs over real kl-roadgraph.v1.bin (311k nodes, L=24, coverage 0.980)
started: after Phase 8-03 integration of ALT steering into astar.ts
## Eliminated
- hypothesis: "ALT reduces expansions; the slowdown is purely per-node cost" — evidence: bench2 counters show expansions INCREASE +15.7% with ALT (66,286 -> 76,671/route; every routed pair; +51% on mid-valley->bukit-bintang); unidirectional control shows the SAME tables PRUNE (0.59x) — the increase is specific to the shared-average BH
- hypothesis: "per-search setup fills cause the regression" — evidence: git ea9584d shows hfCache/hbCache/pf/big arrays existed pre-ALT; measured fills 1.7 ms/search; goalToU/origToU are Float64Array(24) — trivial
- hypothesis: "NaN-sentinel float compare path is float-heavy" — evidence: violation dump shows sentinel handling is a CORRECTNESS problem (family-set variance), not a cost problem; branches are predictable
- hypothesis: "L=32 worse because of table size alone" — evidence: per-node read count scales with L (2×32 vs 2×24 scattered DRAM reads) — consistent with +20% time at L=32; secondary to RC-1/RC-2
## Evidence
- timestamp: 2026-10-25T21:05Z
checked: git ea9584d (pre-ALT astar.ts) vs current
found: pre-ALT potential() = same hfCache/hbCache/pf structure, bound body = pure metersTo; ONLY delta is the ALT L-scan in bound()
implication: the entire regression lives inside bound() + its per-search constant tables
- timestamp: 2026-10-25T21:10Z
checked: real gate measure-route-cpu.mjs run today on this host
found: p95 1037.618 ms (campaign record: 152.788) — host ~7x slower than campaign conditions; bench 'shipped' variant 1124 ms = within 8% of real gate today
implication: bench faithful; use ratios, quote environment caveat
- timestamp: 2026-10-25T21:20Z
checked: bench2 (clean per-variant functions, 25-route seeded set, best-of-3)
found: hav p50 37.6/p95 363.2/mean 121.2, exp 66,286 | shipped 123.2/899.4/332.7, exp 76,671 (+15.7%) | transposed+merged+pfCache 72.3/546.5/197.3 | no parity failures (bench seconds == shipped seconds all pairs)
implication: costlier nodes (~2.5 µs/new-node scan) + more nodes (+15.7%) both real; transposed recovers 41% but still +63% over hav
- timestamp: 2026-10-25T21:25Z
checked: unidirectional A* probes (hav vs max(hav,ALT))
found: uni expansions alt/hav = 0.59 (54,447 -> 31,927 mean); ratio < 1 on 21/23 pairs
implication: tables + bounds genuinely prune UNIDIRECTIONALLY; the BH (h_f-h_b)/2 average is what flips the sign
- timestamp: 2026-10-25T21:35Z
checked: bench3 alpha sweep (max(hav, a*ALT), transposed, real seconds vs shipped)
found: a=0.25/0.5 returned +3.48 s suboptimal on sunway-pyramid-to-pavilion-kl (1120.89 vs 1117.41); a=0 and a=1 clean on these 25 pairs
implication: intermediate scaling TRIGGERS the latent inconsistency stochastically — a soundness mechanism exists beyond the fromTable issue
- timestamp: 2026-10-25T21:45Z
checked: bench5/bench6 unit-correct per-edge consistency check + violation dump
found: a=0: 0/0 violations | a=0.25: 56 bwd (worst 66.3 s) | a=1 SHIPPED: 334 fwd (worst 1026 s) + 11,369 bwd (worst 774 s); all dumped violations have to[v,l*]=0xffff with to[u,l*] finite; table triangle check: 0 violations on all sampled landmarks
implication: per-node sentinel skip = per-node family-set change = the inconsistency; tables exact; code comment's own "no per-node l-mask" law is violated by the sentinel skip
- timestamp: 2026-10-25T21:50Z
checked: bench7 shipped-vs-Dijkstra-oracle on 200 fresh seeded cross-quadrant pairs
found: shipped 1/194 SUBOPTIMAL (+0.278%, pair 141683->292552: 1859.3 vs 1854.1); pure-hav control 0/194
implication: RC-1 is a LIVE correctness bug, not just latent; D-03's fixed corpus is insufficient coverage
- timestamp: 2026-10-25T21:55Z
checked: bench8 landmark column census
found: 0/24 clean columns; each column ~6,285-6,292 sentinels (2.02%); 6,285 nodes reach NO landmark (one disconnected island fragment)
implication: search-scoped clean-column dropping degrades to exactly haversine on this binary (sound); ALT can only be live on a rebuilt binary whose anchors have sentinel-free columns — impossible while the island exists (08-05's no-filler proof generalizes)
## Resolution
root_cause: TWO defects share one surface (potential()/bound() in astar.ts). RC-1 CORRECTNESS: the per-node sentinel skip (`if (tv === 0xffff) continue`) varies the active landmark family set PER NODE — exactly the "per-node ℓ-mask" the code's own comment forbids — so the toTable bound is NOT cross-edge consistent: when node u has finite to[u,ℓ] and neighbor v has to[v,ℓ]=0xffff (v is in the 6,285-node island, 2.02%), h_f(u) stays at the ALT bound (up to 1,905 s) while h_f(v) degrades to haversine → measured 334 forward + 11,369 backward consistency violations on real edges (worst 1,026 s / 774 s); shipped routeAstar returns SUBOPTIMAL seconds on 1/194 fresh random pairs (+0.278% vs Dijkstra oracle, pair 141683->292552); pure-haversine control 0/194. Tables are triangle-exact (0 violations all-landmark sweep) — the skip logic is the bug. D-03 green = sampling luck (recorded pairs avoid the island). RC-2 PERFORMANCE (the 4×): per-new-node bound() = 2 passes x 24 scattered u16 reads (row-major [l*n+v], 622 KB stride) into the 14.9 MB table > 12 MB L3 → DRAM-bound ~2.5 µs/new-node x 77k new nodes ≈ +190-250 ms; PLUS expansions INCREASE +15.7% (all 23 routed pairs; +51% worst) — the shared-Ikeda average (h_f-h_b)/2 flattens the max-composite bound's directional guidance (unidirectional control: SAME bounds PRUNE 0.59x — tables good, BH-average config is the problem). Setup fills 1.7 ms negligible.
fix: F1 (soundness, in-constraint, reverseFor-precedent): per-graph WeakMap memo `reachableTo[l] = (to-column has zero 0xffff)` (O(L*n) once per isolate); per-search active-landmark list = clean columns only; bound() loops the active list with NO per-node sentinel branch (constant-table NaN goal/origin checks retained). On the current binary 0/24 columns are clean -> potentials degrade to exactly haversine -> sound (0/194 control), restores pre-ALT CPU band, A1 fixture (strongly connected) keeps ALT live so the logic stays tested. F2 (performance, for future clean-column binaries): transposed toTable [v*L+l] per-graph memo (14.9 MB one-time, 104-192 ms build) + merged single-pass (read tv once, update both bestG/bestO) + single pfCache Float64Array replacing hfCache/hbCache/pf — measured mean 332.7 -> 197.3 ms (-41%), bound cost 3.5 -> 0.31 µs/node, bit-identical potential values (verified: identical expansions + seconds on all 25 pairs). REQUIRED TEST WORK: A9's island-fixture assertions must be re-specified (island columns are dirty -> ALT drops search-scoped everywhere in that fixture); add a strongly-connected fixture for ALT-active coverage; add the bench7-style seeded random-pair Dijkstra-oracle sweep (200 pairs) as a real-graph optimality pin (this is the gate that would have caught RC-1; D-03's fixed corpus cannot).
verification: bench1/2/3/5/6/7/8 throwaway harnesses in /tmp/opencode/alt-bench/ (never committed); bench replica parity: shipped seconds matched on all 25 gate pairs, no PARITY FAIL lines; alpha-sweep consistency check (unit-correct) pins the violation mechanism to per-node sentinel skips; alpha=0.25/0.5 reproduced REAL suboptimality (+3.48 s on sunway-pyramid-to-pavilion-kl) proving the mechanism end-to-end; bench8 census: 0/24 clean columns, 6,285 no-reach nodes, 2.02% sentinel rate per column. Environment caveat: this host measured the real gate ~7x slower than the 08-05 campaign record (p95 1037.6 ms today vs 152.788 recorded) — all ratios self-consistent within-session; bench-shipped matched real-gate-today within 8%.
files_changed: []