Indexing CPython took about three times as long on 1.6.2 as at 290e03f
(193 s vs 59 s wall, 8.3 GB vs 3.5 GB peak on a 16-thread Windows box).
A CPU profile put ~290 s of the resolver workers' 412 s on two Python
checks that first shipped in 1.6.2:
- pythonExternalWrites (#2291) read every .py file once per module
global its own module types. CPython has 2,375 Python files against
the resolver's 1,000-entry content cache, so every read went to disk
(184 s).
- isPythonLocallyBound (#2198, #2219) re-stripped the calling file for
every (function, name), and, asked once per same-named candidate by
fitsPythonCallShape, re-derived the enclosing function each time:
3.55M calls for 53K distinct answers (104 s).
Now:
- A global's own module is read first; when its bindings already leave
the type unknown (319 of 379 globals on CPython), no other module is
read.
- One pass per resolution indexes which Python files spell a name after
a dot or a quote, and which write through .__dict__; the write scan
visits only those files, in the same path order. Comment stripping
only blanks text, so any write the scan accepts is spelled that way in
the raw text; a non-ASCII name still reads every file. Index keys are
flat copies (a regex capture would pin its whole file).
- The repo files an import names are resolved once per mapping, in a
WeakMap that lives as long as the resolver's import cache keeps it.
- isPythonLocallyBound keeps the current file's stripped lines and
per-name module-level answer, and answers a ref once.
Verification: nodes, edges and unresolved_refs dumps of CPython and
pretix are byte-identical to 6560052a. Medians of 3 interleaved CPython
runs on a loaded machine: 290e03f 100 s / 271 s CPU / 3.3 GB, 6560052a
322 s / 801 s / 7.9 GB, this change 123 s / 361 s / 5.5 GB. In the CPU
profile pythonExternalWrites drops 184 s -> 16 s and
isPythonLocallyBound 104 s -> 13 s. Most of the remaining gap to
290e03f on CPython is JS checks running on its bundled minified d3
(matchDestructuredCallResult, #2334; jsFunctionLocalScope from #2226).
__tests__/python-resolution-work.test.ts counts the work instead of
timing it: reads of untouched files, strips of the calling file, and
node lookups with 16 same-named candidates (4 / 2 / 8 with the fix,
19 / 18 / 128 on main). Each count fails when only its own part of the
fix is reverted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>