@@ -204,25 +204,36 @@ the resolution pipeline.
204204drop + associated malloc/free). Combined with the double-walk
205205fix, should bring examples/demo.php from ~ 9.5s to ~ 5-6s.
206206
207- #### Phase 4d: Reduce string formatting overhead
208-
209- ** Impact: Medium. Effort: Low-Medium.**
210-
211- 3% self-time in ` core::fmt::write ` + 1.4% in ` format_inner ` +
212- 1.3% in ` Ustr::from ` (atom interning). These come from:
213-
214- - ` format!("{}\\{}", ns, name) ` in ` ClassInfo::fqn() ` — called
215- on every resolution. Already mitigated by returning ` Atom ` but
216- the initial intern still allocates.
217- - Type string construction during template substitution.
218- - ` name.to_string() ` calls throughout the resolution pipeline.
219-
220- Plan: audit hot-path ` format!() ` calls and replace with
221- pre-computed or cached values where possible.
222-
223- ### Profiling data (current, release build, examples/demo.php)
224-
225- Measured after Phase 4a + double-walk fix:
207+ #### Phase 4d: Cache the FQN on ClassInfo ✓
208+
209+ Added a cached ` fqn: Option<Atom> ` field to ` ClassInfo ` . Every
210+ hot-path ` .fqn() ` call previously ran ` format!("{}\\{}", ns, name) `
211+ plus an ` Ustr::from ` intern lookup for namespaced classes; the
212+ resulting ` String ` allocation/free showed up across the
213+ ` malloc ` /` memmove ` /` free ` self-time. ` name ` and ` file_namespace ` are
214+ only ever set at the two parse/index-load stamping sites
215+ (` parser/ast_update.rs ` , ` resolution.rs ` ) and never mutated
216+ afterwards, so ` cache_fqn() ` is called there once the namespace is
217+ final and the cache can never go stale. ` fqn() ` returns the cached
218+ value when present and falls back to computing it on demand for the
219+ handful of synthetic classes that never pass through a stamping site
220+ (` __object_shape ` , ` stdClass ` , etc.), which behave exactly as before.
221+ Excluded from ` signature_eq ` since it is derived from ` name ` +
222+ ` file_namespace ` , both already compared there.
223+
224+ Measured: ` analyze examples/demo.php ` dropped from ~ 1.75s to ~ 1.59s
225+ (release build), memory flat.
226+
227+ The remaining string-formatting costs are structural rather than
228+ incidental and are split out into Phase 4g below.
229+
230+ ### Profiling data (release build, examples/demo.php)
231+
232+ ` analyze examples/demo.php ` currently completes in ** ~ 1.6s** (release
233+ build) after Phases 4a–4d landed. The historical breakdown below was
234+ measured after Phase 4a + the double-walk fix, before the Phase 4c
235+ ClassInfo Arc-ification and the Phase 4d FQN cache eliminated most of
236+ the allocation churn that dominated it:
226237
227238| Phase | Wall time | Notes |
228239| ---| ---| ---|
@@ -232,13 +243,13 @@ Measured after Phase 4a + double-walk fix:
232243| Slow diagnostics | 0.6s | (was 9.4s before double-walk fix) |
233244| ** Total** | ** ~ 9.5s** | target: < 3s |
234245
235- Instrumentation data from the scope walk:
246+ Instrumentation data from that scope walk:
236247
237248- 8,726 calls to ` resolve_rhs_expression ` (8.3s top-level time)
238249- 5,559 resolved-class cache hits, 396 misses
239250- Subject pipeline fallback: 1 call (negligible)
240251
241- The 8.3s is dominated by allocation churn (ClassInfo cloning)
252+ The 8.3s was dominated by allocation churn (ClassInfo cloning)
242253inside the type resolution pipeline, not by resolution logic.
243254
244255---
@@ -338,6 +349,30 @@ The problem is needing them for every runtime analysis thread.
338349- No stack overflows on the full test suite or the largest available
339350 project.
340351
352+ #### Phase 4g: Carry ` Atom ` in ` PhpType::Named `
353+
354+ ** Impact: Low-Medium. Effort: Medium.**
355+
356+ Deferred from Phase 4d. With the FQN now cached, the residual
357+ per-substitution string cost is ` PhpType::Named(cls.fqn().to_string()) `
358+ (and the equivalent patterns in ` template_subs.rs ` , ` return_types.rs ` ,
359+ ` inheritance/mod.rs ` , ` forward_walk/scope_state.rs ` ,
360+ ` rhs_resolution/mod.rs ` , and the Laravel builder providers). Each of
361+ these turns a cached ` Atom ` back into an owned ` String ` because
362+ ` PhpType::Named ` holds a ` String ` .
363+
364+ Changing ` PhpType::Named ` (and the analogous per-member cache keys such
365+ as ` format!("{}::{}", class_fqn, method.name) ` in ` target_cache.rs ` /
366+ ` property_access.rs ` ) to carry ` Atom ` would let the resolution pipeline
367+ thread interned names end-to-end without re-allocating. This touches a
368+ core enum used throughout the type engine, so it is a standalone
369+ refactor rather than part of the 4d fqn-cache change.
370+
371+ ** Success criteria:**
372+ - ` PhpType::Named ` carries an interned name (no ` String `
373+ reallocation on the substitution hot path).
374+ - No test regressions.
375+
341376---
342377
343378
0 commit comments