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:
Colby Mchenry
2026-07-21 00:02:02 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent 1aa4de6eaa
commit abb0a916f7
2 changed files with 35 additions and 2 deletions
+34 -2
View File
@@ -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]!;