perf(parser): use SmallVec for duplicate default export detection#16801
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
093c9b1 to
2b7836f
Compare
CodSpeed Performance ReportMerging #16801 will not alter performanceComparing Summary
Footnotes
|
There was a problem hiding this comment.
Overall the change is small and reasonable, but the current SmallVec usage still eagerly collects all default-export spans. If you want the perf tweak to be maximally effective, consider short-circuiting once duplication is detected (or only collecting all labels when needed).
Additional notes (2)
-
Performance |
crates/oxc_parser/src/module_record.rs:57-63
SmallVecis introduced for a simplelen() > 1check, but this still collects all default-export spans. If a module has many default exports (pathological, but possible), this will still iterate and store everything (and will heap-allocate once it exceeds inline capacity). Since the only behavior difference is emitting an error with labels, consider an approach that short-circuits after the second hit for the common/valid case while still producing good diagnostics (e.g., collect first span, detect second, then optionally continue collecting only if you want all labels). This keeps the perf win but avoids extra work in invalid cases too. -
Performance |
crates/oxc_parser/src/module_record.rs:57-63
The change toSmallVecis fine, butwith_labels(default_exports)will typically consume anIntoIteratorof labels. It’s safer and clearer to pass an iterator (or slice) explicitly, rather than relying onSmallVec’s trait impls staying compatible with the diagnostics API. This also avoids accidentally movingdefault_exportsif future code wants to use it afterwith_labels.
Also, this function only needs to know whether there are 2+ defaults; collecting all spans is unnecessary work in degenerate cases with many exports. You can short-circuit once you’ve found a second default, while still preserving the labels you want to show.
Summary of changes
What changed
- Added
smallvecas a dependency foroxc_parser(crates/oxc_parser/Cargo.toml). - Switched duplicate
defaultexport span collection inModuleRecordBuilderfromstd::vec::VectoSmallVec<[Span; 2]>to avoid heap allocation in the common case (crates/oxc_parser/src/module_record.rs). - Updated imports to include
use smallvec::SmallVec;.
There was a problem hiding this comment.
Pull request overview
This PR optimizes memory allocation in the parser's duplicate default export detection by using SmallVec<[Span; 2]> instead of std::vec::Vec<_>. This avoids heap allocation for the common case where modules have 0-2 default exports (the typical scenario being 0 or 1 valid export).
Key Changes:
- Replaced
VecwithSmallVecwith inline capacity of 2 for collecting default export spans - Added
smallvecdependency tooxc_parsercrate
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
crates/oxc_parser/src/module_record.rs |
Changed collection type from Vec to SmallVec<[Span; 2]> for default export spans |
crates/oxc_parser/Cargo.toml |
Added smallvec workspace dependency |
Cargo.lock |
Updated lock file to reflect new dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Boshen
left a comment
There was a problem hiding this comment.
default_exports is not used elsewhere, why not .count() 🤔 ?
we use it for the labels in L63 |
|
@CharlieHelps change to use |
Expand this to see my work.
|
|
Updated the PR to use ChangesVerification# Cargo check: ok
$ cargo ck
# Tests (oxc_parser): 54 passed
$ cargo test -p oxc_parser --all-featuresSkipped |
7856ab2 to
ed80c3f
Compare
Merge activity
|
ed80c3f to
1ec15da
Compare
…6801) Avoid heap allocation for the common case (0-2 default exports) by using SmallVec with inline capacity of 2 instead of Vec. 🤖 generated with help from Claude Opus 4.5
1ec15da to
225f229
Compare
### 🚀 Features - d209c21 allocator: Add cap to FixedSizeAllocatorPool and block when exhausted (#17023) (Cameron) - fb2af91 allocator: Add bitset utils (#17042) (zhaoting zhou) - c16082c tasks/compat_data: Integrate `node-compat-table` (#16831) (Boshen) - 5586823 span: Extract TS declaration file check to its own function (#17037) (camchenry) - 3d2b492 minifier: Fold iife arrow functions in call expressions (#16477) (Armano) - 67e9f9e codegen: Keep comments on the export specifiers (#16943) (夕舞八弦) - cb515fa parser: Improve error message for `yield` as identifier usage (#16950) (sapphi-red) - dcc856b parser: Add help for `new_dynamic_import` error (#16949) (sapphi-red) - c3c79f8 parser: Improve import attribute value error message (#16948) (sapphi-red) - 291b57b ast_tools: Generate TS declaration files for deserializer and walk files (#16912) (camc314) - 74eae13 minifier: Remove unused import specifiers (#16797) (camc314) ### 🐛 Bug Fixes - fb9e193 linter: OOM problems with custom plugins (#17082) (overlookmotel) - e59132b parser/napi: Fix lazy deser (#17069) (overlookmotel) - a92faf0 ast_tools: Support `u128` in `assert_layouts` generator (#17050) (overlookmotel) - 47b4c2f minifier/docs: Correct hyperlink path in OPTIMIZATIONS.md (#16986) (GRK) - 3002649 transformer/typescript: Remove unused import equals declaration (#16776) (Dunqing) - 5a2af88 regular_expression: Correct named capture group reference error (#16952) (sapphi-red) ### ⚡ Performance - b657bb6 allocator: Reduce time `Mutex` lock is held in `FixedSizeAllocatorPool::get` (#17079) (overlookmotel) - 1f3b19b ast: `#[ast]` macro use `#[repr(transparent)]` for single-field structs (#17052) (overlookmotel) - 225f229 parser: Use SmallVec for duplicate default export detection (#16801) (camc314) ### 📚 Documentation - a9c419f traverse: Update safety comments (#16944) (overlookmotel) Co-authored-by: overlookmotel <557937+overlookmotel@users.noreply.github.com>

Avoid heap allocation for the common case (0-2 default exports) by using
SmallVec with inline capacity of 2 instead of Vec.
🤖 generated with help from Claude Opus 4.5