Skip to content

Commit 3061acb

Browse files
committed
perf(linter/plugins): pre-populate cache of EnterExit objects (#20187)
Perf optimization to visitor compilation. Previously in the main loop for non-leaf visitors in `finalizeCompiledVisitor`, every turn of the loop had to check if cache contains an `EnterExit` object it can reuse, and to create a new one if not. Instead, pre-populate the cache of `EnterExit` object *before* that main loop, to ensure there are always enough in the main loop. After warmup over the first few files, the cache will be populated with enough `EnterExit` objects to service every file, and the "populate" loop will be skipped entirely. This converts what was 1 branch per loop iteration to 1 highly predictable branch per file.
1 parent c73912b commit 3061acb

File tree

1 file changed

+19
-17
lines changed

1 file changed

+19
-17
lines changed

apps/oxlint/src-js/plugins/visitor.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ let hasActiveVisitors = false;
215215
// `compiledVisitor` may contain many `{ enter, exit }` objects.
216216
// Use this cache to reuse those objects across all visitor compilations.
217217
//
218-
// `enterExitObjectCacheNextIndex` is the index of first object in cache which is currently unused.
219-
// It may point to the end of the cache array.
218+
// `activeNonLeafVisitorsCount` is the number of populated non-leaf visitors in `compiledVisitor`,
219+
// and therefore the number of `EnterExit` objects currently in use.
220220
const enterExitObjectCache: EnterExit[] = [];
221-
let enterExitObjectCacheNextIndex = 0;
221+
let activeNonLeafVisitorsCount = 0;
222222

223223
// `VisitProp` object cache.
224224
//
@@ -417,21 +417,23 @@ export function finalizeCompiledVisitor(): VisitorState {
417417
compiledVisitor[typeId] = mergeVisitFns(compilingLeafVisitor[typeId]!);
418418
}
419419

420+
// Populate `enterExitObjectCache` with enough entries for all non-leaf visitors.
421+
// After warming up over first few files, the cache will be large enough to service all files,
422+
// and this loop will be skipped. This avoids the main loop below from having to branch repeatedly
423+
// on whether there are enough `EnterExit` objects in cache, and to create one if not.
424+
activeNonLeafVisitorsCount = activeNonLeafVisitorTypeIds.length;
425+
while (enterExitObjectCache.length < activeNonLeafVisitorsCount) {
426+
enterExitObjectCache.push({ enter: null, exit: null });
427+
}
428+
420429
// Merge visitors for non-leaf nodes
421-
for (let i = activeNonLeafVisitorTypeIds.length - 1; i >= 0; i--) {
430+
for (let i = 0; i < activeNonLeafVisitorsCount; i++) {
422431
const typeId = activeNonLeafVisitorTypeIds[i]!;
423432
const entry = compilingNonLeafVisitor[typeId - LEAF_NODE_TYPES_COUNT]!;
424433

425-
// Get or create enter-exit object.
426-
// Use an existing object from cache if available, otherwise create a new one.
427-
let enterExit: EnterExit;
428-
if (enterExitObjectCacheNextIndex < enterExitObjectCache.length) {
429-
enterExit = enterExitObjectCache[enterExitObjectCacheNextIndex];
430-
} else {
431-
enterExit = { enter: null, exit: null };
432-
enterExitObjectCache.push(enterExit);
433-
}
434-
enterExitObjectCacheNextIndex++;
434+
// Use enter-exit object from cache. Loop above ensures cache is filled with enough objects.
435+
const enterExit = enterExitObjectCache[i];
436+
debugAssertIsNonNull(enterExit, "`enterExit` should not be null");
435437

436438
// Merge enter and exit visitors
437439
const enterArr = entry.enter;
@@ -487,13 +489,13 @@ export function resetCompiledVisitor(): void {
487489
// Reset `compiledVisitor` array
488490
compiledVisitor.fill(null);
489491

490-
// Reset enter+exit objects
491-
for (let i = 0; i < enterExitObjectCacheNextIndex; i++) {
492+
// Reset `EnterExit` objects
493+
for (let i = 0; i < activeNonLeafVisitorsCount; i++) {
492494
const enterExit = enterExitObjectCache[i];
493495
enterExit.enter = null;
494496
enterExit.exit = null;
495497
}
496-
enterExitObjectCacheNextIndex = 0;
498+
activeNonLeafVisitorsCount = 0;
497499
}
498500

499501
// Array used by `mergeVisitFns` and `mergeCfgVisitFns` to store visit functions extracted from an array of `VisitProp`s.

0 commit comments

Comments
 (0)