perf(synthesis): provably-empty pass gates + prefilters — render/expo/rn/mybatis stop scanning repos they can't match; iface memo (#1389)
Store-arc round 2 (#1388 follow-up). The synthesis pool barrier on dubbo carried ~1.4s of passes that provably could not emit an edge for the project: reactRenderEdges fanned out over every class before checking for a render method (now: one indexed name lookup bounds candidates — not a language gate, Java Litho-style render+setState still matches); expo/rn cross-platform pairing streamed every method row without the languages their edges require (now registry-gated: expo needs swift AND kotlin file-languages, rn needs a JS-family caller for isBridge); mybatis built its full java-method index before discovering there were no mapper-XML methods (now collects the XML side first). ifaceEdges — real work — stops re-fetching a hub interface's methods once per implementer and skips supertype-less classes before any per-class lookup. dubbo warm wall 8.49-8.79 → 8.14-8.24s (n=3/arm, caffeinated); barrier 784→435ms; the full removed pass work lands on low-core envelopes where synthesis runs sequentially. Dumps byte-identical: dubbo old-vs-new, pooled-vs-sequential, kernel-vs-wasm (441,270 rows) + excalidraw JSX-live control (89,903 rows, 46 react-render edges reproduced). Suite 2,689 ×2 with CODEGRAPH_KERNEL_EXPECT=1. Also ships the diagnostics that located the round (zero cost when off): CODEGRAPH_RESOLVE_PROFILE=2 attributes per-ref time to resolveOne's strategies (stage:*) and the name-matcher's sub-matchers (nm:*); CODEGRAPH_SYNTH_TIMINGS now prints the store worker's decode-vs-SQL split. Killed by measurement, recorded in the PR: import-failure negative cache (both-outcome names exist — static imports resolve via instance-method on jvm-miss), jvm-miss early return (1,939 later-strategy edges), jsxEdges language gate (Java generics text produces jsx edges), and §4d buffer→bind on Spring repos (extract() hook forces the decoded path — kernel=0 bundles measured). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
27c3c55436
commit
082ea65f3a
@@ -2047,6 +2047,45 @@ export function matchFuzzy(
|
||||
/** ArkUI attribute-helper decorators a `.attr(...)` chain may resolve to. */
|
||||
const ARKUI_ATTRIBUTE_DECORATORS = new Set(['Extend', 'Styles', 'AnimatableExtend', 'Builder']);
|
||||
|
||||
/**
|
||||
* CODEGRAPH_RESOLVE_PROFILE=2 sub-stage attribution for matchReference's
|
||||
* strategy pipeline (`nm:<stage>|<refKind>|hit/miss`). Module-global because
|
||||
* the matcher is a free function; each thread (main + every pool worker) has
|
||||
* its own module instance, and dumpNameMatcherProfile is invoked from
|
||||
* ReferenceResolver.dumpResolveProfile so worker tables surface too.
|
||||
*/
|
||||
const NM_PROFILE: Map<string, { n: number; ns: bigint }> | null =
|
||||
process.env.CODEGRAPH_RESOLVE_PROFILE === '2' ? new Map() : null;
|
||||
|
||||
function nmTimed(stage: string, ref: UnresolvedRef, fn: () => ResolvedRef | null): ResolvedRef | null {
|
||||
if (!NM_PROFILE) return fn();
|
||||
const t0 = process.hrtime.bigint();
|
||||
const r = fn();
|
||||
const dt = process.hrtime.bigint() - t0;
|
||||
const key = `nm:${stage}|${ref.referenceKind}|${r ? 'hit' : 'miss'}`;
|
||||
const slot = NM_PROFILE.get(key);
|
||||
if (slot) {
|
||||
slot.n++;
|
||||
slot.ns += dt;
|
||||
} else {
|
||||
NM_PROFILE.set(key, { n: 1, ns: dt });
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Dump this thread's matchReference sub-stage table to stderr (no-op unless =2). */
|
||||
export function dumpNameMatcherProfile(label: string): void {
|
||||
if (!NM_PROFILE || NM_PROFILE.size === 0) return;
|
||||
const rows = [...NM_PROFILE.entries()]
|
||||
.map(([k, v]) => ({ k, n: v.n, ms: Number(v.ns / 1_000_000n) }))
|
||||
.sort((a, b) => b.ms - a.ms);
|
||||
for (const r of rows) {
|
||||
console.error(
|
||||
`[resolve-profile] ${label} ${r.k}: n=${r.n} total=${(r.ms / 1000).toFixed(1)}s avg=${((r.ms * 1000) / Math.max(1, r.n)).toFixed(0)}µs`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function matchReference(
|
||||
ref: UnresolvedRef,
|
||||
context: ResolutionContext
|
||||
@@ -2119,18 +2158,18 @@ export function matchReference(
|
||||
let result: ResolvedRef | null;
|
||||
|
||||
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|
||||
result = matchByFilePath(ref, context);
|
||||
result = nmTimed('filePath', ref, () => matchByFilePath(ref, context));
|
||||
if (result) return result;
|
||||
|
||||
// 1. Qualified name match (highest confidence)
|
||||
result = matchByQualifiedName(ref, context);
|
||||
result = nmTimed('qualifiedName', ref, () => matchByQualifiedName(ref, context));
|
||||
if (result) return result;
|
||||
|
||||
// 1b. C++ chained call whose receiver is another call — `Foo::instance().bar()`
|
||||
// encoded as `Foo::instance().bar` by the extractor (#645). Resolve the
|
||||
// receiver's type from what the inner call returns, then the method on it.
|
||||
if (ref.language === 'cpp' || ref.language === 'c') {
|
||||
result = matchCppCallChain(ref, context);
|
||||
result = nmTimed('cppChain', ref, () => matchCppCallChain(ref, context));
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
@@ -2139,7 +2178,7 @@ export function matchReference(
|
||||
// type is the factory's `self` (PHP `: self`/`: static`, Rust `-> Self`) or
|
||||
// concrete return type.
|
||||
if (ref.language === 'php' || ref.language === 'rust') {
|
||||
result = matchScopedCallChain(ref, context);
|
||||
result = nmTimed('scopedChain', ref, () => matchScopedCallChain(ref, context));
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
@@ -2161,20 +2200,20 @@ export function matchReference(
|
||||
ref.language === 'objc' ||
|
||||
ref.language === 'pascal'
|
||||
) {
|
||||
result = matchDottedCallChain(ref, context);
|
||||
result = nmTimed('dottedChain', ref, () => matchDottedCallChain(ref, context));
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
// 2. Method call pattern
|
||||
result = matchMethodCall(ref, context);
|
||||
result = nmTimed('methodCall', ref, () => matchMethodCall(ref, context));
|
||||
if (result) return result;
|
||||
|
||||
// 3. Exact name match
|
||||
result = matchByExactName(ref, context);
|
||||
result = nmTimed('exactName', ref, () => matchByExactName(ref, context));
|
||||
if (result) return result;
|
||||
|
||||
// 4. Fuzzy match (lowest confidence)
|
||||
result = matchFuzzy(ref, context);
|
||||
result = nmTimed('fuzzy', ref, () => matchFuzzy(ref, context));
|
||||
if (result) return result;
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user