Skip to content

Commit 97f2180

Browse files
authoredMar 15, 2025··
test(es/minifier): Add a benchmark for real-world inputs (#10204)
**Description:** I pinned the commit using the hash to make updating the inputs explicit.
1 parent 99ba555 commit 97f2180

File tree

4 files changed

+173
-75
lines changed

4 files changed

+173
-75
lines changed
 

‎.changeset/selfish-badgers-repair.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
swc_core: patch
3+
swc_ecma_transforms_optimization: patch
4+
---
5+
6+
test(es/minifier): Add a benchmark for real-world inputs

‎crates/swc_ecma_minifier/benches/full.rs

+155-60
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@
22
33
extern crate swc_malloc;
44

5-
use std::fs::read_to_string;
5+
use std::{
6+
fs::read_to_string,
7+
path::{Path, PathBuf},
8+
process::Command,
9+
};
610

711
use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion};
8-
use swc_common::{errors::HANDLER, sync::Lrc, FileName, Mark, SourceMap};
9-
use swc_ecma_ast::Program;
12+
use swc_common::{sync::Lrc, FileName, Mark, SourceMap, GLOBALS};
1013
use swc_ecma_codegen::text_writer::JsWriter;
1114
use swc_ecma_minifier::{
1215
optimize,
1316
option::{ExtraOptions, MangleOptions, MinifyOptions},
1417
};
15-
use swc_ecma_parser::parse_file_as_module;
18+
use swc_ecma_parser::parse_file_as_program;
1619
use swc_ecma_transforms_base::{fixer::fixer, resolver};
20+
use swc_ecma_utils::parallel::{Parallel, ParallelExt};
21+
use testing::CARGO_TARGET_DIR;
22+
use walkdir::WalkDir;
1723

18-
pub fn bench_files(c: &mut Criterion) {
24+
fn bench_libs(c: &mut Criterion) {
1925
let mut group = c.benchmark_group("es/minifier/libs");
2026
group.sample_size(10);
2127

@@ -45,65 +51,151 @@ pub fn bench_files(c: &mut Criterion) {
4551
bench_file("vue");
4652
}
4753

48-
criterion_group!(files, bench_files);
49-
criterion_main!(files);
54+
fn bench_real(c: &mut Criterion) {
55+
let input_dir = CARGO_TARGET_DIR.join("swc-minifier-inputs");
5056

51-
fn run(src: &str) {
52-
testing::run_test2(false, |cm, handler| {
53-
HANDLER.set(&handler, || {
54-
let fm = cm.new_source_file(FileName::Anon.into(), src.into());
55-
56-
let unresolved_mark = Mark::new();
57-
let top_level_mark = Mark::new();
58-
59-
let program = parse_file_as_module(
60-
&fm,
61-
Default::default(),
62-
Default::default(),
63-
None,
64-
&mut Vec::new(),
65-
)
66-
.map_err(|err| {
67-
err.into_diagnostic(&handler).emit();
68-
})
69-
.map(Program::Module)
70-
.map(|module| module.apply(resolver(unresolved_mark, top_level_mark, false)))
57+
git_clone(
58+
"https://github.com/kdy1/swc-minifier-inputs.git",
59+
"a967ebba1668d1f78e1b5077bdbdce6ad0bfcaee",
60+
&input_dir,
61+
);
62+
63+
let mut group = c.benchmark_group("es/minifier/real");
64+
group.sample_size(10);
65+
66+
let files = expand_dirs(&input_dir);
67+
let sources = files
68+
.iter()
69+
.map(|path| read_to_string(path).unwrap())
70+
.collect::<Vec<_>>();
71+
72+
group.bench_function("es/minifier/real/sequential", |b| {
73+
b.iter(|| {
74+
// We benchmark full time, including time for creating cm, handler
75+
76+
for src in &sources {
77+
run(src);
78+
}
79+
})
80+
});
81+
82+
group.bench_function("es/minifier/real/parallel", |b| {
83+
b.iter(|| {
84+
// We benchmark full time, including time for creating cm, handler
85+
86+
GLOBALS.set(&Default::default(), || {
87+
Worker::default().maybe_par(0, &*sources, |_, src| run(src));
88+
});
89+
})
90+
});
91+
}
92+
93+
#[derive(Default, Clone, Copy)]
94+
struct Worker {}
95+
96+
impl Parallel for Worker {
97+
fn create(&self) -> Self {
98+
*self
99+
}
100+
101+
fn merge(&mut self, _other: Self) {}
102+
}
103+
104+
fn git_clone(url: &str, commit: &str, path: &Path) {
105+
if !path.join(".git").exists() {
106+
let status = Command::new("git")
107+
.arg("clone")
108+
.arg(url)
109+
.arg(path)
110+
.status()
71111
.unwrap();
72112

73-
let output = optimize(
74-
program,
75-
cm.clone(),
76-
None,
77-
None,
78-
&MinifyOptions {
79-
rename: false,
80-
compress: Some(Default::default()),
81-
mangle: Some(MangleOptions {
82-
props: None,
83-
top_level: Some(true),
84-
keep_class_names: false,
85-
keep_fn_names: false,
86-
keep_private_props: false,
87-
ie8: false,
88-
..Default::default()
89-
}),
90-
wrap: false,
91-
enclose: false,
92-
},
93-
&ExtraOptions {
94-
unresolved_mark,
95-
top_level_mark,
96-
mangle_name_cache: None,
97-
},
98-
);
99-
100-
let output = output.apply(fixer(None));
101-
102-
let code = print(cm, &[output], true);
103-
104-
black_box(code);
105-
Ok(())
113+
if !status.success() {
114+
panic!("failed to clone `{}` to `{}`", url, path.display());
115+
}
116+
}
117+
118+
let status = Command::new("git")
119+
.current_dir(path)
120+
.args(["checkout", commit])
121+
.status()
122+
.unwrap();
123+
124+
if !status.success() {
125+
panic!("failed to checkout `{}` in `{}`", commit, path.display());
126+
}
127+
}
128+
129+
/// Return the whole input files as abolute path.
130+
fn expand_dirs(dir: &Path) -> Vec<PathBuf> {
131+
WalkDir::new(dir)
132+
.into_iter()
133+
.filter_map(Result::ok)
134+
.filter_map(|entry| {
135+
if entry.metadata().map(|v| v.is_file()).unwrap_or(false) {
136+
Some(entry.into_path())
137+
} else {
138+
None
139+
}
106140
})
141+
.filter(|path| path.extension().map(|ext| ext == "js").unwrap_or(false))
142+
.collect::<Vec<_>>()
143+
}
144+
145+
fn run(src: &str) {
146+
testing::run_test2(false, |cm, handler| {
147+
let fm = cm.new_source_file(FileName::Anon.into(), src.into());
148+
149+
let unresolved_mark = Mark::new();
150+
let top_level_mark = Mark::new();
151+
152+
let Ok(program) = parse_file_as_program(
153+
&fm,
154+
Default::default(),
155+
Default::default(),
156+
None,
157+
&mut Vec::new(),
158+
)
159+
.map_err(|err| {
160+
err.into_diagnostic(&handler).emit();
161+
})
162+
.map(|program| program.apply(resolver(unresolved_mark, top_level_mark, false))) else {
163+
return Ok(());
164+
};
165+
166+
let output = optimize(
167+
program,
168+
cm.clone(),
169+
None,
170+
None,
171+
&MinifyOptions {
172+
rename: false,
173+
compress: Some(Default::default()),
174+
mangle: Some(MangleOptions {
175+
props: None,
176+
top_level: Some(true),
177+
keep_class_names: false,
178+
keep_fn_names: false,
179+
keep_private_props: false,
180+
ie8: false,
181+
..Default::default()
182+
}),
183+
wrap: false,
184+
enclose: false,
185+
},
186+
&ExtraOptions {
187+
unresolved_mark,
188+
top_level_mark,
189+
mangle_name_cache: None,
190+
},
191+
);
192+
193+
let output = output.apply(fixer(None));
194+
195+
let code = print(cm, &[output], true);
196+
197+
black_box(code);
198+
Ok(())
107199
})
108200
.unwrap();
109201
}
@@ -126,3 +218,6 @@ fn print<N: swc_ecma_codegen::Node>(cm: Lrc<SourceMap>, nodes: &[N], minify: boo
126218

127219
String::from_utf8(buf).unwrap()
128220
}
221+
222+
criterion_group!(bench_all, bench_libs, bench_real);
223+
criterion_main!(bench_all);

‎crates/swc_ecma_minifier/tests/exec.rs

-4
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,6 @@ fn run(
9898
config: Option<&str>,
9999
mangle: Option<MangleOptions>,
100100
) -> Option<Program> {
101-
let _ = rayon::ThreadPoolBuilder::new()
102-
.thread_name(|i| format!("rayon-{}", i + 1))
103-
.build_global();
104-
105101
let compress_config = config.map(|config| parse_compressor_config(cm.clone(), config).1);
106102

107103
let fm = cm.new_source_file(FileName::Anon.into(), input.into());

‎crates/swc_ecma_transforms_optimization/src/simplify/dce/mod.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@ use swc_common::{
1010
Mark, SyntaxContext, DUMMY_SP,
1111
};
1212
use swc_ecma_ast::*;
13-
use swc_ecma_transforms_base::{
14-
helpers::{Helpers, HELPERS},
15-
perf::{cpu_count, ParVisitMut, Parallel},
16-
};
13+
use swc_ecma_transforms_base::perf::{cpu_count, Parallel};
1714
use swc_ecma_utils::{
18-
collect_decls, find_pat_ids, ExprCtx, ExprExt, IsEmpty, ModuleItemLike, StmtLike, Value::Known,
15+
collect_decls, find_pat_ids, parallel::ParallelExt, ExprCtx, ExprExt, IsEmpty, ModuleItemLike,
16+
StmtLike, Value::Known,
1917
};
2018
use swc_ecma_visit::{
2119
noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
@@ -673,6 +671,13 @@ impl TreeShaker {
673671
self.changed = true;
674672
}
675673
}
674+
675+
fn visit_mut_par<N>(&mut self, threshold: usize, nodes: &mut [N])
676+
where
677+
N: Send + Sync + VisitMutWith<Self>,
678+
{
679+
self.maybe_par(threshold, nodes, |v, n| n.visit_mut_with(v));
680+
}
676681
}
677682

678683
impl VisitMut for TreeShaker {
@@ -918,9 +923,7 @@ impl VisitMut for TreeShaker {
918923
data.subtract_cycles();
919924
self.data = Arc::new(data);
920925

921-
HELPERS.set(&Helpers::new(true), || {
922-
m.visit_mut_children_with(self);
923-
})
926+
m.visit_mut_children_with(self);
924927
}
925928

926929
fn visit_mut_module_item(&mut self, n: &mut ModuleItem) {
@@ -981,9 +984,7 @@ impl VisitMut for TreeShaker {
981984
data.subtract_cycles();
982985
self.data = Arc::new(data);
983986

984-
HELPERS.set(&Helpers::new(true), || {
985-
m.visit_mut_children_with(self);
986-
})
987+
m.visit_mut_children_with(self);
987988
}
988989

989990
fn visit_mut_stmt(&mut self, s: &mut Stmt) {

0 commit comments

Comments
 (0)
Please sign in to comment.