-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Summary
I did a static performance audit of crates/swc_ecma_transformer/src and found several hotspots that can become expensive on large files/classes.
Most of the risk comes from:
Vec::insert(0, ...)/Vec::insert(i, ...)patterns inside loops (potentialO(n^2)shifts)- Repeated
remove/insertonVec<ModuleItem>while iterating - Repeated full subtree scans in hot hooks
- Linear membership checks on
VecwhereHashSetwould beO(1)
Findings
1) Repeated front insertion in object-rest lowering (O(n^2) shifting)
In multiple hooks, we prepend statements by repeatedly calling insert(0, stmt):
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:279crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:309crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:357crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:386crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:410crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:444crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:576crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:614
If many statements are prepended to large bodies, this repeatedly shifts the tail.
Suggestion: build a new vector with capacity and append [prepended..., original...], or use a small deque-style buffering strategy before one final materialization.
2) remove(i) + multiple insert(...) in exit_module_items (O(n^2) risk)
object_rest_spread::exit_module_items mutates items in-place with middle removes/inserts while iterating:
remove(i):crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:478- multiple
insert(...):crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:493,:509,:513 - whole loop:
crates/swc_ecma_transformer/src/es2018/object_rest_spread.rs:460-537
On modules with many exports, this can degenerate into repeated shifts.
Suggestion: transform via a new output vector (Vec::with_capacity) in a single pass.
3) Statement injector currently performs repeated Vec::insert in reverse pass
common/statement_injector.rs collects insertion plans, then applies them using many insert calls:
- module items insertion loop:
crates/swc_ecma_transformer/src/common/statement_injector.rs:129-155 - statement insertion loop:
crates/swc_ecma_transformer/src/common/statement_injector.rs:171-197
This is also shift-heavy and can become O(n^2) with many injected statements.
Suggestion: rebuild the vector once, emitting before + original + after per element.
4) Private-property pass stores membership sets as Vec and uses repeated .contains
ClassData tracks methods and statics as Vec<Atom>:
- definitions:
crates/swc_ecma_transformer/src/es2022/private_property_in_object.rs:132,:135 - membership checks in hot path:
:144,:149,:371,:372
These checks are linear in the number of private members.
Suggestion: switch methods and statics to FxHashSet<Atom> (or maintain both ordered vec + set only if ordering is needed).
5) Full subtree scan in enter_assign_pat for every assign pattern
private_property_in_object performs an explicit visitor walk on each AssignPat.right:
crates/swc_ecma_transformer/src/es2022/private_property_in_object.rs:282-290
This adds extra traversals in addition to the main transform traversal.
Suggestion: avoid per-node ad-hoc scans by carrying a lightweight context flag from the main traversal or restricting this scan to cases that already matched a fast precheck.
6) Function-name transform does full usage scan per candidate function/class
function_name calls IdentUsageFinder::find for each anonymous fn/class assignment candidate:
crates/swc_ecma_transformer/src/es2015/function_name.rs:111crates/swc_ecma_transformer/src/es2015/function_name.rs:126
On code with many such candidates and large bodies, this introduces repeated full-body scans.
Suggestion: add cheap fast-path guards before full scan, or cache/short-circuit when the candidate name cannot appear.
7) Async-to-generator can do a second full recursive walk to replace this
In constructor-arrow cases using this, we run an additional recursive replacement pass over the arrow body:
- call site:
crates/swc_ecma_transformer/src/es2017/async_to_generator.rs:189-195 - recursive walkers:
:876-1331
This is semantically valid but can be expensive for large nested arrow bodies.
Suggestion: integrate replacement into the main traversal path when possible, or narrow second-pass scope with stronger prechecks.
Suggested follow-up
- Add micro-bench fixtures for:
- many function params/object-rest cases
- many export declarations requiring rewrite
- large class with many private members and
#x in ychecks
- Measure before/after with wall time + allocation counts.
- Prioritize findings ecmascript parser #1, Clean up #2, EcmaScript lexer is inefficient #3, Transformers for ecmascript #4 first (highest expected impact).
Scope note
This report is static-analysis-only (no runtime profiling yet).