Skip to content

Commit 76fe139

Browse files
authoredJul 20, 2024··
perf(es/minifier): Pre-allocate collections (#9289)
**Related issue:** - Inspiration: oxc-project/oxc#4328
1 parent 07376c6 commit 76fe139

File tree

16 files changed

+127
-52
lines changed

16 files changed

+127
-52
lines changed
 

‎Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/swc_allocator/src/collections.rs

+3
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ pub type FxHashMap<K, V> = HashMap<K, V, FxBuildHasher>;
3131

3232
/// Faster `HashSet` which uses `FxHasher`.
3333
pub type FxHashSet<T> = HashSet<T, FxBuildHasher>;
34+
35+
/// Re-export for convenience.
36+
pub use hashbrown::hash_map;

‎crates/swc_ecma_minifier/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ serde = { workspace = true, features = ["derive"] }
5151
serde_json = { workspace = true }
5252
tracing = { workspace = true }
5353

54+
swc_allocator = { version = "0.1.7", path = "../swc_allocator", default-features = false }
5455
swc_atoms = { version = "0.6.5", path = "../swc_atoms" }
5556
swc_common = { version = "0.36.0", path = "../swc_common" }
5657
swc_config = { version = "0.1.13", path = "../swc_config", features = [
@@ -76,6 +77,7 @@ pretty_assertions = { workspace = true }
7677
walkdir = { workspace = true }
7778

7879
codspeed-criterion-compat = { workspace = true }
80+
swc_allocator = { version = "0.1.7", path = "../swc_allocator" }
7981
swc_ecma_testing = { version = "0.25.0", path = "../swc_ecma_testing" }
8082
swc_malloc = { version = "0.5.10", path = "../swc_malloc" }
8183
testing = { version = "0.38.0", path = "../testing" }

‎crates/swc_ecma_minifier/benches/full.rs

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate swc_malloc;
55
use std::fs::read_to_string;
66

77
use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion};
8+
use swc_allocator::Allocator;
89
use swc_common::{errors::HANDLER, sync::Lrc, FileName, Mark, SourceMap};
910
use swc_ecma_codegen::text_writer::JsWriter;
1011
use swc_ecma_minifier::{
@@ -25,6 +26,9 @@ pub fn bench_files(c: &mut Criterion) {
2526
group.bench_function(&format!("es/minifier/libs/{}", name), |b| {
2627
b.iter(|| {
2728
// We benchmark full time, including time for creating cm, handler
29+
let allocator = Allocator::default();
30+
let _guard = unsafe { allocator.guard() };
31+
2832
run(&src)
2933
})
3034
});

‎crates/swc_ecma_minifier/src/compress/optimize/iife.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ impl Optimizer<'_> {
863863
args: &mut [ExprOrSpread],
864864
exprs: &mut Vec<Box<Expr>>,
865865
) -> Vec<VarDeclarator> {
866-
let mut vars = Vec::new();
866+
let mut vars = Vec::with_capacity(params.len());
867867

868868
for (idx, param) in params.iter().enumerate() {
869869
let arg = args.get_mut(idx).map(|arg| arg.expr.take());

‎crates/swc_ecma_minifier/src/compress/optimize/sequences.rs

+24-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::mem::take;
1+
use std::{iter::once, mem::take};
22

3-
use swc_common::{util::take::Take, Spanned, DUMMY_SP};
3+
use swc_common::{pass::Either, util::take::Take, Spanned, DUMMY_SP};
44
use swc_ecma_ast::*;
55
use swc_ecma_usage_analyzer::{
66
alias::{collect_infects_from, AccessKind, AliasConfig},
@@ -565,61 +565,59 @@ impl Optimizer<'_> {
565565
&mut self,
566566
s: &'a mut Stmt,
567567
options: &CompressOptions,
568-
) -> Option<Vec<Mergable<'a>>> {
568+
) -> Option<Either<impl Iterator<Item = Mergable<'a>>, std::iter::Once<Mergable<'a>>>> {
569569
Some(match s {
570570
Stmt::Expr(e) => {
571571
if self.options.sequences()
572572
|| self.options.collapse_vars
573573
|| self.options.side_effects
574574
{
575-
vec![Mergable::Expr(&mut e.expr)]
575+
Either::Right(once(Mergable::Expr(&mut e.expr)))
576576
} else {
577577
return None;
578578
}
579579
}
580580
Stmt::Decl(Decl::Var(v)) => {
581581
if options.reduce_vars || options.collapse_vars {
582-
v.decls.iter_mut().map(Mergable::Var).collect()
582+
Either::Left(v.decls.iter_mut().map(Mergable::Var))
583583
} else {
584584
return None;
585585
}
586586
}
587587
Stmt::Return(ReturnStmt { arg: Some(arg), .. }) => {
588-
vec![Mergable::Expr(arg)]
588+
Either::Right(once(Mergable::Expr(arg)))
589589
}
590590

591-
Stmt::If(s) if options.sequences() => {
592-
vec![Mergable::Expr(&mut s.test)]
593-
}
591+
Stmt::If(s) if options.sequences() => Either::Right(once(Mergable::Expr(&mut s.test))),
594592

595593
Stmt::Switch(s) if options.sequences() => {
596-
vec![Mergable::Expr(&mut s.discriminant)]
594+
Either::Right(once(Mergable::Expr(&mut s.discriminant)))
597595
}
598596

599597
Stmt::For(s) if options.sequences() => {
600598
if let Some(VarDeclOrExpr::Expr(e)) = &mut s.init {
601-
vec![Mergable::Expr(e)]
599+
Either::Right(once(Mergable::Expr(e)))
602600
} else {
603601
return None;
604602
}
605603
}
606604

607605
Stmt::ForOf(s) if options.sequences() => {
608-
vec![Mergable::Expr(&mut s.right)]
606+
Either::Right(once(Mergable::Expr(&mut s.right)))
609607
}
610608

611609
Stmt::ForIn(s) if options.sequences() => {
612-
vec![Mergable::Expr(&mut s.right)]
610+
Either::Right(once(Mergable::Expr(&mut s.right)))
613611
}
614612

615613
Stmt::Throw(s) if options.sequences() => {
616-
vec![Mergable::Expr(&mut s.arg)]
614+
Either::Right(once(Mergable::Expr(&mut s.arg)))
617615
}
618616

619617
Stmt::Decl(Decl::Fn(f)) => {
620618
// Check for side effects
621619

622-
vec![Mergable::FnDecl(f)]
620+
Either::Right(once(Mergable::FnDecl(f)))
623621
}
624622

625623
_ => return None,
@@ -646,12 +644,9 @@ impl Optimizer<'_> {
646644
return;
647645
}
648646

649-
let mut exprs = Vec::new();
650-
let mut buf = Vec::new();
651-
652-
for stmt in stmts.iter_mut() {
653-
let is_end = matches!(
654-
stmt.as_stmt(),
647+
fn is_end(s: Option<&Stmt>) -> bool {
648+
matches!(
649+
s,
655650
Some(
656651
Stmt::If(..)
657652
| Stmt::Throw(..)
@@ -661,7 +656,14 @@ impl Optimizer<'_> {
661656
| Stmt::ForIn(..)
662657
| Stmt::ForOf(..)
663658
) | None
664-
);
659+
)
660+
}
661+
662+
let mut exprs = Vec::new();
663+
let mut buf = Vec::new();
664+
665+
for stmt in stmts.iter_mut() {
666+
let is_end = is_end(stmt.as_stmt());
665667
let can_skip = match stmt.as_stmt() {
666668
Some(Stmt::Decl(Decl::Fn(..))) => true,
667669
_ => false,

‎crates/swc_ecma_minifier/src/compress/optimize/util.rs

+8
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,14 @@ impl VisitMut for NormalMultiReplacer<'_> {
533533
}
534534
}
535535
}
536+
537+
fn visit_mut_stmt(&mut self, node: &mut Stmt) {
538+
if self.vars.is_empty() {
539+
return;
540+
}
541+
542+
node.visit_mut_children_with(self);
543+
}
536544
}
537545

538546
pub(crate) fn replace_id_with_expr<N>(node: &mut N, from: Id, to: Box<Expr>) -> Option<Box<Expr>>

‎crates/swc_ecma_minifier/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ mod mode;
7777
pub mod option;
7878
mod pass;
7979
mod program_data;
80+
mod size_hint;
8081
pub mod timing;
8182
mod util;
8283

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[derive(Debug, Default, Clone, Copy)]
2+
pub struct SizeHint {}

‎crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs

+21
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,29 @@ impl Analyzer {
4040
}
4141
}
4242

43+
fn reserve_decl(&mut self, len: usize, belong_to_fn_scope: bool) {
44+
if belong_to_fn_scope {
45+
match self.scope.kind {
46+
ScopeKind::Fn => {
47+
self.scope.reserve_decl(len);
48+
}
49+
ScopeKind::Block => {
50+
self.hoisted_vars.reserve(len);
51+
}
52+
}
53+
} else {
54+
self.scope.reserve_decl(len);
55+
}
56+
}
57+
4358
fn add_usage(&mut self, id: Id) {
4459
self.scope.add_usage(id);
4560
}
4661

62+
fn reserve_usage(&mut self, len: usize) {
63+
self.scope.reserve_usage(len);
64+
}
65+
4766
fn with_scope<F>(&mut self, kind: ScopeKind, op: F)
4867
where
4968
F: FnOnce(&mut Analyzer),
@@ -66,6 +85,7 @@ impl Analyzer {
6685
op(&mut v);
6786
if !v.hoisted_vars.is_empty() {
6887
debug_assert!(matches!(v.scope.kind, ScopeKind::Block));
88+
self.reserve_usage(v.hoisted_vars.len());
6989
v.hoisted_vars.clone().into_iter().for_each(|id| {
7090
// For variables declared in block scope using `var` and `function`,
7191
// We should create a fake usage in the block to prevent conflicted
@@ -74,6 +94,7 @@ impl Analyzer {
7494
});
7595
match self.scope.kind {
7696
ScopeKind::Fn => {
97+
self.reserve_decl(v.hoisted_vars.len(), true);
7798
v.hoisted_vars
7899
.into_iter()
79100
.for_each(|id| self.add_decl(id, true));

‎crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs

+10
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ impl Scope {
6868
}
6969
}
7070

71+
pub(crate) fn reserve_decl(&mut self, len: usize) {
72+
self.data.all.reserve(len);
73+
74+
self.data.queue.reserve(len);
75+
}
76+
7177
pub(super) fn add_usage(&mut self, id: Id) {
7278
if id.0 == "arguments" {
7379
return;
@@ -76,6 +82,10 @@ impl Scope {
7682
self.data.all.insert(id);
7783
}
7884

85+
pub(crate) fn reserve_usage(&mut self, len: usize) {
86+
self.data.all.reserve(len);
87+
}
88+
7989
/// Copy `children.data.all` to `self.data.all`.
8090
pub(crate) fn prepare_renaming(&mut self) {
8191
self.children.iter_mut().for_each(|child| {

‎crates/swc_ecma_transforms_base/src/rename/collector.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl Visit for IdCollector {
2121

2222
fn visit_export_namespace_specifier(&mut self, _: &ExportNamespaceSpecifier) {}
2323

24-
fn visit_expr(&mut self, n: &Expr) {
24+
fn visit_bin_expr(&mut self, n: &BinExpr) {
2525
maybe_grow_default(|| n.visit_children_with(self));
2626
}
2727

@@ -135,10 +135,14 @@ where
135135
fn visit_expr(&mut self, node: &Expr) {
136136
let old = self.is_pat_decl;
137137
self.is_pat_decl = false;
138-
maybe_grow_default(|| node.visit_children_with(self));
138+
node.visit_children_with(self);
139139
self.is_pat_decl = old;
140140
}
141141

142+
fn visit_bin_expr(&mut self, node: &BinExpr) {
143+
maybe_grow_default(|| node.visit_children_with(self));
144+
}
145+
142146
fn visit_fn_decl(&mut self, node: &FnDecl) {
143147
node.visit_children_with(self);
144148

‎crates/swc_ecma_transforms_base/src/resolver/mod.rs

+20-22
Original file line numberDiff line numberDiff line change
@@ -1913,10 +1913,9 @@ impl VisitMut for Hoister<'_, '_> {
19131913
/// that there is already an global declaration of Ic when deal with the try
19141914
/// block.
19151915
fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
1916-
let mut other_items = Vec::new();
1917-
1918-
for item in items {
1919-
match item {
1916+
let others = items
1917+
.iter_mut()
1918+
.filter_map(|item| match item {
19201919
ModuleItem::Stmt(Stmt::Decl(Decl::Var(v)))
19211920
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
19221921
decl: Decl::Var(v),
@@ -1930,6 +1929,7 @@ impl VisitMut for Hoister<'_, '_> {
19301929
) =>
19311930
{
19321931
item.visit_mut_with(self);
1932+
None
19331933
}
19341934

19351935
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(..)))
@@ -1938,37 +1938,35 @@ impl VisitMut for Hoister<'_, '_> {
19381938
..
19391939
})) => {
19401940
item.visit_mut_with(self);
1941+
None
19411942
}
1942-
_ => {
1943-
other_items.push(item);
1944-
}
1945-
}
1946-
}
1943+
_ => Some(item),
1944+
})
1945+
.collect::<Vec<_>>();
19471946

1948-
for other_item in other_items {
1949-
other_item.visit_mut_with(self);
1950-
}
1947+
others.into_iter().for_each(|item| {
1948+
item.visit_mut_with(self);
1949+
});
19511950
}
19521951

19531952
/// see docs for `self.visit_mut_module_items`
19541953
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
1955-
let mut other_stmts = Vec::new();
1956-
1957-
for item in stmts {
1958-
match item {
1954+
let others = stmts
1955+
.iter_mut()
1956+
.filter_map(|item| match item {
19591957
Stmt::Decl(Decl::Var(..)) => {
19601958
item.visit_mut_with(self);
1959+
None
19611960
}
19621961
Stmt::Decl(Decl::Fn(..)) => {
19631962
item.visit_mut_with(self);
1963+
None
19641964
}
1965-
_ => {
1966-
other_stmts.push(item);
1967-
}
1968-
}
1969-
}
1965+
_ => Some(item),
1966+
})
1967+
.collect::<Vec<_>>();
19701968

1971-
for other_stmt in other_stmts {
1969+
for other_stmt in others {
19721970
other_stmt.visit_mut_with(self);
19731971
}
19741972
}

‎crates/swc_ecma_transforms_optimization/src/debug.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use swc_ecma_ast::*;
66
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
77

88
/// Assert in debug mode. This is noop in release build.
9+
#[cfg_attr(not(debug_assertions), inline(always))]
910
pub fn debug_assert_valid<N>(node: &N)
1011
where
1112
N: VisitWith<AssertValid>,

‎crates/swc_ecma_usage_analyzer/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
//! This crate is an internal crate that is extracted just to reduce compile
2+
//! time. Do not use this crate directly, and this package does not follow
3+
//! semver.
4+
15
#![allow(clippy::mutable_key_type)]
26

37
pub mod alias;

‎xtask/src/bench.rs

+19-5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ pub(super) struct BenchCmd {
3232
#[clap(long)]
3333
instrument: bool,
3434

35+
/// Instrument using https://github.com/mstange/samply
36+
#[clap(long)]
37+
samply: bool,
38+
3539
#[clap(long)]
3640
features: Vec<String>,
3741

@@ -50,7 +54,17 @@ impl BenchCmd {
5054
}
5155

5256
fn build_cmd(&self) -> Result<Command> {
53-
let mut cmd = if self.instrument {
57+
let mut cmd = if self.samply {
58+
// ddt profile instruments cargo -t time
59+
let mut cmd = Command::new("ddt");
60+
cmd.arg("profile").arg("samply").arg("cargo");
61+
62+
if !self.debug {
63+
cmd.arg("--release");
64+
}
65+
66+
cmd
67+
} else if self.instrument {
5468
// ddt profile instruments cargo -t time
5569
let mut cmd = Command::new("ddt");
5670
cmd.arg("profile").arg("instruments").arg("cargo");
@@ -61,9 +75,6 @@ impl BenchCmd {
6175
cmd.arg("--release");
6276
}
6377

64-
// TODO: This should use cargo metadata
65-
cmd.current_dir(repository_root()?.join("crates").join(&self.package));
66-
6778
cmd
6879
} else {
6980
let mut cmd = Command::new("cargo");
@@ -90,12 +101,15 @@ impl BenchCmd {
90101
cmd.arg("--features").arg(f);
91102
}
92103

93-
if self.instrument {
104+
if self.samply || self.instrument {
94105
cmd.arg("--").arg("--bench").args(&self.args);
95106
} else {
96107
cmd.arg("--").args(&self.args);
97108
}
98109

110+
// TODO: This should use cargo metadata
111+
cmd.current_dir(repository_root()?.join("crates").join(&self.package));
112+
99113
Ok(cmd)
100114
}
101115
}

0 commit comments

Comments
 (0)
Please sign in to comment.