perf(semantic): pre-reserve unresolved_references using Stats::references (#22580)

## Summary

The `unresolved_references` `Vec<(Ident, ReferenceId)>` inside `SemanticBuilder` grows from `cap = 0` via the default doubling policy — that's ~13 reallocations to reach the ~5k references of a moderately-sized TypeScript file (193 KB `binder.ts`), copying a cumulative ~16k entries (~250 KB of memory traffic) just to grow the buffer.

`Stats` already knows the exact reference count up front (via `Stats::count` or passed in by the caller via `SemanticBuilder::with_stats`). Pre-reserving with `stats.references` eliminates all the growth reallocations.

8-line internal change. No public API impact.

## Benchmark

### `cargo bench --bench semantic` (local Criterion)

| Bench | main | this PR | Δ |
|---|---|---|---|
| `RadixUI.jsx` (3 KB) | 3.155 µs | 3.046 µs | **−3.5%** |
| `react.development.js` (72 KB) | 136.47 µs | 133.78 µs | **−2.0%** |
| `cal.com.tsx` (1 MB TSX) | 2.7414 ms | 2.7064 ms | **−1.3%** |
| `binder.ts` (193 KB TS) | 314.90 µs | 309.46 µs | **−1.7%** |

All Criterion intervals non-overlapping.

### CodSpeed (precise, instruction-count based, from #22571 which tested the same commit)

| Bench | Δ |
|---|---|
| `pipeline[binder.ts]` | **−4.34%** |
| `semantic[binder.ts]` | **−9.75%** |
| `semantic[react.development.js]` | **−5.06%** |

CodSpeed's instruction-count measurement is more sensitive to allocator-call elimination than wall-clock Criterion, so the numbers are larger but consistent in direction.

## Why `reserve_exact` (not `reserve`)

- `stats.references` is the exact total push count (asserted in debug builds via `Stats::assert_accurate`). No need for amortized growth headroom.
- Matches the existing precedent in `Scoping::reserve` which uses `reserve_exact` on arena-allocated structures.
- Sub-100% reserves (tested 50%) were empirically *worse* than no reserve — the single resulting realloc is a large-block alloc that misses the size-class fast paths the small doubling reallocs hit. So 100% is the optimum.

## Test Plan

- [x] `cargo test -p oxc_semantic --lib --tests` — 71 tests pass
- [x] `cargo bench --bench semantic` — wins above
- [x] CodSpeed re-run on commit (positive results above)

AI disclosure: drafted with Claude Code, reviewed manually.
This commit is contained in:
Dunqing committed 2026-05-19 13:54:22 +00:00
1 parent 652530a283
commit 7a4120ed92
4 files changed
+21 -12

No files matched your search

+1
View File
@@ -288,6 +288,7 @@ impl<'a> SemanticBuilder<'a> {
stats.references as usize,
stats.scopes as usize,
);
self.unresolved_references.reserve_exact(stats.references as usize);
// Visit AST to generate scopes tree etc
self.visit_program(program);
@@ -16,6 +16,14 @@ impl<'a> UnresolvedReferences<'a> {
Self { references: Vec::new() }
}
/// Reserve exactly `additional` more slots in the underlying `Vec`.
/// Avoids growth reallocations when the expected count is known up-front
/// (typically from [`crate::Stats::count`]).
#[inline]
pub(crate) fn reserve_exact(&mut self, additional: usize) {
self.references.reserve_exact(additional);
}
/// Push an unresolved reference to the flat list.
#[inline]
pub(crate) fn push(&mut self, name: Ident<'a>, reference_id: ReferenceId) {
@@ -1,14 +1,14 @@
File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs
---------------------------------------------------------------------------------------------------------------------------
checker.ts | 2.92 MB || 226 | 37 || 152662 | 28244
checker.ts | 2.92 MB || 226 | 7 || 152662 | 28244
cal.com.tsx | 1.06 MB || 16287 | 46 || 37156 | 4570
cal.com.tsx | 1.06 MB || 16287 | 20 || 37156 | 4570
RadixUIAdoptionSection.jsx | 2.52 kB || 24 | 6 || 31 | 6
RadixUIAdoptionSection.jsx | 2.52 kB || 24 | 0 || 31 | 6
pdf.mjs | 567.30 kB || 4016 | 417 || 47465 | 7740
pdf.mjs | 567.30 kB || 4016 | 393 || 47465 | 7740
antd.js | 6.69 MB || 7624 | 1657 || 333609 | 69349
antd.js | 6.69 MB || 7624 | 1627 || 333609 | 69349
binder.ts | 193.08 kB || 47 | 23 || 7076 | 824
binder.ts | 193.08 kB || 47 | 1 || 7076 | 824
@@ -1,14 +1,14 @@
File | File size || Sys allocs | Sys reallocs || Arena allocs | Arena reallocs
---------------------------------------------------------------------------------------------------------------------------
checker.ts | 2.92 MB || 2309 | 18 || 0 | 0
checker.ts | 2.92 MB || 2309 | 3 || 0 | 0
cal.com.tsx | 1.06 MB || 12064 | 22 || 0 | 0
cal.com.tsx | 1.06 MB || 12064 | 10 || 0 | 0
RadixUIAdoptionSection.jsx | 2.52 kB || 10 | 3 || 0 | 0
RadixUIAdoptionSection.jsx | 2.52 kB || 10 | 0 || 0 | 0
pdf.mjs | 567.30 kB || 1401 | 208 || 0 | 0
pdf.mjs | 567.30 kB || 1401 | 196 || 0 | 0
antd.js | 6.69 MB || 2455 | 18 || 0 | 0
antd.js | 6.69 MB || 2455 | 3 || 0 | 0
binder.ts | 193.08 kB || 196 | 11 || 0 | 0
binder.ts | 193.08 kB || 196 | 0 || 0 | 0