Reduce allocations on hot path-normalization and overlay-update paths - #63895
Draft
no-yan wants to merge 1 commit into
Draft
Reduce allocations on hot path-normalization and overlay-update paths#63895no-yan wants to merge 1 commit into
no-yan wants to merge 1 commit into
Conversation
Three allocation-count hot spots identified while profiling a large real-world corpus (VSCode): - overlayfs: avoid re-hashing the same content twice on every LSP edit (newOverlay already computes the hash). - tspath.GetNormalizedAbsolutePath: accumulate the slow (dot/dot-dot) path via a pre-sized []byte buffer instead of repeated string concatenation, turning O(k^2) copying into O(k) appends for paths with many segments after the first change. - module.normalizePathForCJSResolution: use GetBaseFileName for the final-segment check instead of splitting the whole path. GetNormalizedAbsolutePath is covered by the existing FuzzGetNormalizedAbsolutePath against the reference implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 20, 2026
Author
|
@microsoft-github-policy-service agree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
AI disclosure:
I asked Claude Fable 5 to identify potential improvements to reduce allocation churn. I then selected some of those suggestions and asked Fable to implement them.
I'll keep these PRs as drafts until I've verified them myself.
First of a short series of PRs addressing allocation churn identified while profiling
tsgo -p src/tsconfig.json --noEmiton the VS Code repo.Motivation
In the longest GC cycle,
GODEBUG=gctrace=2shows that each span scan processes only a small fraction of the objects in the span, limiting Green Tea GC's ability to exploit spatial locality.Specifically, each span scan visits on average only 3–30% of the span's object slots for the highest-traffic size classes, and about 20% of scanned objects fall back to per-object (sparse) scanning.
These statistics can reflect two distinct causes: low live-object density in scanned spans, or poor batching of same-span marks.
Pointer-chasing structures such as linked lists can cause poor batching because objects in the same span may be discovered at different times. As a result, the same span may be scanned repeatedly with few new objects per pass. The gctrace data alone cannot distinguish between the two causes.
This series targets the former by reducing allocation churn in hot paths identified through profiling.
Per-size-class scan density (gctrace=2, baseline, full vscode/src check)
"Slots visited per pass" = avg objects scanned per span-scan pass ÷
object slots per 8 KiB span.
Totals across all classes: 5.52M span-scanned objects in 396k span scan
passes (13.9 objs/pass average) plus 1.48M sparse-scanned objects —
21.2% of scanned objects take the per-object fallback. The 8 B class
and all classes ≥ 576 B are never span-scanned.
This PR
processChangesre-hashed the overlay content on everyLSP edit even though
newOverlayhad just computed the same hash.Drop the second hash.
tspath.GetNormalizedAbsolutePath: accumulate the slow(dot/dot-dot) path in a pre-sized
[]bytebuffer instead of repeatedstring concatenation, turning O(k²) copying into O(k) appends for
paths with many segments after the first change.
module.normalizePathForCJSResolution: useGetBaseFileNameforthe final-segment check instead of splitting the whole path into a
[]stringjust to look at its last element. This runs for everyrelative import during module resolution.
Results
The CJS normalization chain drops from 240k–293k to 66k–98k allocations
per full vscode/src check (heap-profile attribution, deterministic under
--singleThreaded). The existingBenchmarkGetNormalizedAbsolutePathshows −14 to −34% and far fewer allocations on long/multi-segment paths.
Known tradeoff: short non-normalized toy inputs (e.g.
"/a/./") losethe old zero-alloc string-slice references (6→23 allocs per 20 calls).
Real-world corpora are dominated by long absolute paths, where this
change wins — see the end-to-end numbers below.
Verification
GetNormalizedAbsolutePathis covered by the existing differentialfuzz test against the reference implementation
(
FuzzGetNormalizedAbsolutePath); during development,normalizePathForCJSResolutionpassed a 2.5M–3.3M-case differentialfuzz against the previous implementation.
Series-wide effect (all four PRs combined)
Measured on vscode/src (
--singleThreadedon a quiet machine unlessnoted; warm ABAB pairs):
The system-CPU drop is consistent with the allocation-rate reduction:
fewer span acquisitions mean fewer
madvisecalls on Darwin, which iswhere the effect is largest in parallel mode (a third session showed
−23%). Wall-clock deltas trend favorably but are within run-to-run
variance and are not claimed.
🤖 Generated with Claude Code