perf(resolution): per-context basename index for Lua/Luau require resolution — kong fresh index −16% (#1391)
The full-README competitor matrix ranked lua/kong as the largest legitimate fresh-index gap (2.71×). Stage attribution (RESOLVE_PROFILE=2) pinned it: resolveLuaRequire ran getAllFiles().filter(endsWith) FOUR times per require ref — ~7.5k string suffix scans each, measured at ~0.9ms/ref, hit or miss (2.7s combined over kong's 3k requires). Replace the per-ref full-list scans with a per-context basename → file-paths index (the cobolCopybookIndexes pattern). Buckets preserve getAllFiles() iteration order, so each suffix's candidate filter yields exactly the array the full scan produced — identical matches, identical stable sort, identical winner, dump-proven. kong: 4.07-4.27 → 3.40-3.63s (−16%). Gates: kong old-vs-new dump byte-identical (157,650 rows), kong pooled-vs-sequential identical, Fusion (luau instance-path requires) old-vs-new identical; suite 2,689 ×2 with CODEGRAPH_KERNEL_EXPECT=1. Also registers cobolCopybookIndexes in clearImportResolverMemos — it was never dropped on cache clears, so post-sync copybook lookups could serve a stale file list; cache-drop is the always-safe direction. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1aa4de6eaa
commit
abb0a916f7
@@ -106,6 +106,8 @@ export function clearImportResolverMemos(context: ResolutionContext): void {
|
||||
importPathMemos.delete(context);
|
||||
exportedSymbolMemos.delete(context);
|
||||
fileExportIndexes.delete(context);
|
||||
luaFileBasenameIndexes.delete(context);
|
||||
cobolCopybookIndexes.delete(context);
|
||||
}
|
||||
|
||||
export function resolveImportPath(
|
||||
@@ -181,6 +183,32 @@ function resolveImportPathUncached(
|
||||
*/
|
||||
const cobolCopybookIndexes = new WeakMap<ResolutionContext, Map<string, string[]>>();
|
||||
|
||||
/**
|
||||
* Per-context basename → file-paths index for Lua/Luau require resolution
|
||||
* (cobolCopybookIndexes pattern). resolveLuaRequire previously ran
|
||||
* `getAllFiles().filter(endsWith)` FOUR times per require ref — ~7.5k string
|
||||
* suffix scans each, measured at ~0.9ms/ref (2.7s combined on kong's 3k
|
||||
* requires). Buckets preserve getAllFiles() iteration order so the per-suffix
|
||||
* candidate list filters to exactly the array the full scan produced —
|
||||
* identical matches, identical stable sort, identical winner.
|
||||
*/
|
||||
const luaFileBasenameIndexes = new WeakMap<ResolutionContext, Map<string, string[]>>();
|
||||
|
||||
function luaBasenameIndex(context: ResolutionContext): Map<string, string[]> {
|
||||
let index = luaFileBasenameIndexes.get(context);
|
||||
if (!index) {
|
||||
index = new Map();
|
||||
for (const f of context.getAllFiles()) {
|
||||
const base = f.split('/').pop() ?? '';
|
||||
const paths = index.get(base);
|
||||
if (paths) paths.push(f);
|
||||
else index.set(base, [f]);
|
||||
}
|
||||
luaFileBasenameIndexes.set(context, index);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
function resolveCobolCopybook(
|
||||
member: string,
|
||||
fromFile: string,
|
||||
@@ -1647,14 +1675,18 @@ function resolveLuaRequire(ref: UnresolvedRef, context: ResolutionContext): Reso
|
||||
if (!name) return null;
|
||||
const base = name.includes('.') ? name.replace(/\./g, '/') : name;
|
||||
const suffixes = [`${base}.lua`, `${base}.luau`, `${base}/init.lua`, `${base}/init.luau`];
|
||||
const files = context.getAllFiles();
|
||||
const byBasename = luaBasenameIndex(context);
|
||||
const shared = (a: string, b: string): number => {
|
||||
let i = 0;
|
||||
while (i < a.length && i < b.length && a[i] === b[i]) i++;
|
||||
return i;
|
||||
};
|
||||
for (const suffix of suffixes) {
|
||||
const matches = files.filter((f) => f === suffix || f.endsWith('/' + suffix));
|
||||
// Only files sharing the suffix's basename can match — the bucket is in
|
||||
// getAllFiles() order, so this filter yields exactly what the full-list
|
||||
// scan did.
|
||||
const candidates = byBasename.get(suffix.split('/').pop() ?? '') ?? [];
|
||||
const matches = candidates.filter((f) => f === suffix || f.endsWith('/' + suffix));
|
||||
if (matches.length === 0) continue;
|
||||
matches.sort((x, y) => shared(y, ref.filePath) - shared(x, ref.filePath));
|
||||
const best = matches[0]!;
|
||||
|
||||
Reference in New Issue
Block a user