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

9.6 KiB
Raw Permalink Blame History

status, trigger, created, updated
status trigger created updated
resolved_f1_applied 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. 2026-10-25T00:00:00Z 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 [ln+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(Ln) 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: []