Hypothesis
In callSyncSingle (source-code-visitor.js:87-93), for each matched selector, the code does: `Map.get(name)` → check if array exists → iterate array → call each function. The majority of selectors have exactly 1 registered listener (one rule per selector name). For these single-listener selectors, the array iteration (for loop setup, length check, index increment) is unnecessary overhead.
If we store single-listener selectors differently (as a direct function reference instead of a 1-element array), we can call the function directly without array overhead. This is analogous to how V8 optimizes monomorphic call sites vs megamorphic.
Proposed Change
Modify SourceCodeVisitor to distinguish single-listener entries:
```js
add(name, func) {
const existing = this.#functions.get(name);
if (existing === undefined) {
// First listener: store function directly
this.#functions.set(name, func);
} else if (typeof existing === 'function') {
// Second listener: convert to array
this.#functions.set(name, [existing, func]);
} else {
// Third+: push to existing array
existing.push(func);
}
}
callSyncSingle(name, arg) {
const entry = this.#functions.get(name);
if (entry !== undefined) {
if (typeof entry === 'function') {
entry(arg); // Fast path: single listener
} else {
for (let i = 0; i < entry.length; i++) {
entryi;
}
}
}
}
```
Expected Impact
- Most selectors (e.g., "Identifier", "BlockStatement") have 1-2 listeners from different rules
- For single-listener selectors: eliminates array allocation, length check, index variable
- ~90K nodes × ~3-5 matched selectors per node = ~270K-450K dispatch calls per file
- If 60-70% are single-listener: ~162K-315K calls take the fast path
- Per-call savings: ~5-15ns (branch prediction + no array overhead)
- Conservative estimate: 3-7ms composite improvement
Key Files
- `lib/linter/source-code-visitor.js` — add(), callSyncSingle(), callSync2(), callSync3()
Hypothesis
In callSyncSingle (source-code-visitor.js:87-93), for each matched selector, the code does: `Map.get(name)` → check if array exists → iterate array → call each function. The majority of selectors have exactly 1 registered listener (one rule per selector name). For these single-listener selectors, the array iteration (for loop setup, length check, index increment) is unnecessary overhead.
If we store single-listener selectors differently (as a direct function reference instead of a 1-element array), we can call the function directly without array overhead. This is analogous to how V8 optimizes monomorphic call sites vs megamorphic.
Proposed Change
Modify SourceCodeVisitor to distinguish single-listener entries:
```js
add(name, func) {
const existing = this.#functions.get(name);
if (existing === undefined) {
// First listener: store function directly
this.#functions.set(name, func);
} else if (typeof existing === 'function') {
// Second listener: convert to array
this.#functions.set(name, [existing, func]);
} else {
// Third+: push to existing array
existing.push(func);
}
}
callSyncSingle(name, arg) {
const entry = this.#functions.get(name);
if (entry !== undefined) {
if (typeof entry === 'function') {
entry(arg); // Fast path: single listener
} else {
for (let i = 0; i < entry.length; i++) {
entryi;
}
}
}
}
```
Expected Impact
Key Files