Skip to content

Commit

Permalink
perf(es/minifier): Pre-allocate collections (#9289)
Browse files Browse the repository at this point in the history
**Related issue:**

 - Inspiration: oxc-project/oxc#4328
  • Loading branch information
kdy1 authored Jul 20, 2024
1 parent 07376c6 commit 76fe139
Show file tree
Hide file tree
Showing 16 changed files with 127 additions and 52 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/swc_allocator/src/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ pub type FxHashMap<K, V> = HashMap<K, V, FxBuildHasher>;

/// Faster `HashSet` which uses `FxHasher`.
pub type FxHashSet<T> = HashSet<T, FxBuildHasher>;

/// Re-export for convenience.
pub use hashbrown::hash_map;
2 changes: 2 additions & 0 deletions crates/swc_ecma_minifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tracing = { workspace = true }

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

codspeed-criterion-compat = { workspace = true }
swc_allocator = { version = "0.1.7", path = "../swc_allocator" }
swc_ecma_testing = { version = "0.25.0", path = "../swc_ecma_testing" }
swc_malloc = { version = "0.5.10", path = "../swc_malloc" }
testing = { version = "0.38.0", path = "../testing" }
Expand Down
4 changes: 4 additions & 0 deletions crates/swc_ecma_minifier/benches/full.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ extern crate swc_malloc;
use std::fs::read_to_string;

use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion};
use swc_allocator::Allocator;
use swc_common::{errors::HANDLER, sync::Lrc, FileName, Mark, SourceMap};
use swc_ecma_codegen::text_writer::JsWriter;
use swc_ecma_minifier::{
Expand All @@ -25,6 +26,9 @@ pub fn bench_files(c: &mut Criterion) {
group.bench_function(&format!("es/minifier/libs/{}", name), |b| {
b.iter(|| {
// We benchmark full time, including time for creating cm, handler
let allocator = Allocator::default();
let _guard = unsafe { allocator.guard() };

run(&src)
})
});
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_minifier/src/compress/optimize/iife.rs
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ impl Optimizer<'_> {
args: &mut [ExprOrSpread],
exprs: &mut Vec<Box<Expr>>,
) -> Vec<VarDeclarator> {
let mut vars = Vec::new();
let mut vars = Vec::with_capacity(params.len());

for (idx, param) in params.iter().enumerate() {
let arg = args.get_mut(idx).map(|arg| arg.expr.take());
Expand Down
46 changes: 24 additions & 22 deletions crates/swc_ecma_minifier/src/compress/optimize/sequences.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::mem::take;
use std::{iter::once, mem::take};

use swc_common::{util::take::Take, Spanned, DUMMY_SP};
use swc_common::{pass::Either, util::take::Take, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_usage_analyzer::{
alias::{collect_infects_from, AccessKind, AliasConfig},
Expand Down Expand Up @@ -565,61 +565,59 @@ impl Optimizer<'_> {
&mut self,
s: &'a mut Stmt,
options: &CompressOptions,
) -> Option<Vec<Mergable<'a>>> {
) -> Option<Either<impl Iterator<Item = Mergable<'a>>, std::iter::Once<Mergable<'a>>>> {
Some(match s {
Stmt::Expr(e) => {
if self.options.sequences()
|| self.options.collapse_vars
|| self.options.side_effects
{
vec![Mergable::Expr(&mut e.expr)]
Either::Right(once(Mergable::Expr(&mut e.expr)))
} else {
return None;
}
}
Stmt::Decl(Decl::Var(v)) => {
if options.reduce_vars || options.collapse_vars {
v.decls.iter_mut().map(Mergable::Var).collect()
Either::Left(v.decls.iter_mut().map(Mergable::Var))
} else {
return None;
}
}
Stmt::Return(ReturnStmt { arg: Some(arg), .. }) => {
vec![Mergable::Expr(arg)]
Either::Right(once(Mergable::Expr(arg)))
}

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

Stmt::Switch(s) if options.sequences() => {
vec![Mergable::Expr(&mut s.discriminant)]
Either::Right(once(Mergable::Expr(&mut s.discriminant)))
}

Stmt::For(s) if options.sequences() => {
if let Some(VarDeclOrExpr::Expr(e)) = &mut s.init {
vec![Mergable::Expr(e)]
Either::Right(once(Mergable::Expr(e)))
} else {
return None;
}
}

Stmt::ForOf(s) if options.sequences() => {
vec![Mergable::Expr(&mut s.right)]
Either::Right(once(Mergable::Expr(&mut s.right)))
}

Stmt::ForIn(s) if options.sequences() => {
vec![Mergable::Expr(&mut s.right)]
Either::Right(once(Mergable::Expr(&mut s.right)))
}

Stmt::Throw(s) if options.sequences() => {
vec![Mergable::Expr(&mut s.arg)]
Either::Right(once(Mergable::Expr(&mut s.arg)))
}

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

vec![Mergable::FnDecl(f)]
Either::Right(once(Mergable::FnDecl(f)))
}

_ => return None,
Expand All @@ -646,12 +644,9 @@ impl Optimizer<'_> {
return;
}

let mut exprs = Vec::new();
let mut buf = Vec::new();

for stmt in stmts.iter_mut() {
let is_end = matches!(
stmt.as_stmt(),
fn is_end(s: Option<&Stmt>) -> bool {
matches!(
s,
Some(
Stmt::If(..)
| Stmt::Throw(..)
Expand All @@ -661,7 +656,14 @@ impl Optimizer<'_> {
| Stmt::ForIn(..)
| Stmt::ForOf(..)
) | None
);
)
}

let mut exprs = Vec::new();
let mut buf = Vec::new();

for stmt in stmts.iter_mut() {
let is_end = is_end(stmt.as_stmt());
let can_skip = match stmt.as_stmt() {
Some(Stmt::Decl(Decl::Fn(..))) => true,
_ => false,
Expand Down
8 changes: 8 additions & 0 deletions crates/swc_ecma_minifier/src/compress/optimize/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,14 @@ impl VisitMut for NormalMultiReplacer<'_> {
}
}
}

fn visit_mut_stmt(&mut self, node: &mut Stmt) {
if self.vars.is_empty() {
return;
}

node.visit_mut_children_with(self);
}
}

pub(crate) fn replace_id_with_expr<N>(node: &mut N, from: Id, to: Box<Expr>) -> Option<Box<Expr>>
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_minifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ mod mode;
pub mod option;
mod pass;
mod program_data;
mod size_hint;
pub mod timing;
mod util;

Expand Down
2 changes: 2 additions & 0 deletions crates/swc_ecma_minifier/src/size_hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[derive(Debug, Default, Clone, Copy)]
pub struct SizeHint {}
21 changes: 21 additions & 0 deletions crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,29 @@ impl Analyzer {
}
}

fn reserve_decl(&mut self, len: usize, belong_to_fn_scope: bool) {
if belong_to_fn_scope {
match self.scope.kind {
ScopeKind::Fn => {
self.scope.reserve_decl(len);
}
ScopeKind::Block => {
self.hoisted_vars.reserve(len);
}
}
} else {
self.scope.reserve_decl(len);
}
}

fn add_usage(&mut self, id: Id) {
self.scope.add_usage(id);
}

fn reserve_usage(&mut self, len: usize) {
self.scope.reserve_usage(len);
}

fn with_scope<F>(&mut self, kind: ScopeKind, op: F)
where
F: FnOnce(&mut Analyzer),
Expand All @@ -66,6 +85,7 @@ impl Analyzer {
op(&mut v);
if !v.hoisted_vars.is_empty() {
debug_assert!(matches!(v.scope.kind, ScopeKind::Block));
self.reserve_usage(v.hoisted_vars.len());
v.hoisted_vars.clone().into_iter().for_each(|id| {
// For variables declared in block scope using `var` and `function`,
// We should create a fake usage in the block to prevent conflicted
Expand All @@ -74,6 +94,7 @@ impl Analyzer {
});
match self.scope.kind {
ScopeKind::Fn => {
self.reserve_decl(v.hoisted_vars.len(), true);
v.hoisted_vars
.into_iter()
.for_each(|id| self.add_decl(id, true));
Expand Down
10 changes: 10 additions & 0 deletions crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ impl Scope {
}
}

pub(crate) fn reserve_decl(&mut self, len: usize) {
self.data.all.reserve(len);

self.data.queue.reserve(len);
}

pub(super) fn add_usage(&mut self, id: Id) {
if id.0 == "arguments" {
return;
Expand All @@ -76,6 +82,10 @@ impl Scope {
self.data.all.insert(id);
}

pub(crate) fn reserve_usage(&mut self, len: usize) {
self.data.all.reserve(len);
}

/// Copy `children.data.all` to `self.data.all`.
pub(crate) fn prepare_renaming(&mut self) {
self.children.iter_mut().for_each(|child| {
Expand Down
8 changes: 6 additions & 2 deletions crates/swc_ecma_transforms_base/src/rename/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl Visit for IdCollector {

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

fn visit_expr(&mut self, n: &Expr) {
fn visit_bin_expr(&mut self, n: &BinExpr) {
maybe_grow_default(|| n.visit_children_with(self));
}

Expand Down Expand Up @@ -135,10 +135,14 @@ where
fn visit_expr(&mut self, node: &Expr) {
let old = self.is_pat_decl;
self.is_pat_decl = false;
maybe_grow_default(|| node.visit_children_with(self));
node.visit_children_with(self);
self.is_pat_decl = old;
}

fn visit_bin_expr(&mut self, node: &BinExpr) {
maybe_grow_default(|| node.visit_children_with(self));
}

fn visit_fn_decl(&mut self, node: &FnDecl) {
node.visit_children_with(self);

Expand Down
42 changes: 20 additions & 22 deletions crates/swc_ecma_transforms_base/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1913,10 +1913,9 @@ impl VisitMut for Hoister<'_, '_> {
/// that there is already an global declaration of Ic when deal with the try
/// block.
fn visit_mut_module_items(&mut self, items: &mut Vec<ModuleItem>) {
let mut other_items = Vec::new();

for item in items {
match item {
let others = items
.iter_mut()
.filter_map(|item| match item {
ModuleItem::Stmt(Stmt::Decl(Decl::Var(v)))
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Var(v),
Expand All @@ -1930,6 +1929,7 @@ impl VisitMut for Hoister<'_, '_> {
) =>
{
item.visit_mut_with(self);
None
}

ModuleItem::Stmt(Stmt::Decl(Decl::Fn(..)))
Expand All @@ -1938,37 +1938,35 @@ impl VisitMut for Hoister<'_, '_> {
..
})) => {
item.visit_mut_with(self);
None
}
_ => {
other_items.push(item);
}
}
}
_ => Some(item),
})
.collect::<Vec<_>>();

for other_item in other_items {
other_item.visit_mut_with(self);
}
others.into_iter().for_each(|item| {
item.visit_mut_with(self);
});
}

/// see docs for `self.visit_mut_module_items`
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
let mut other_stmts = Vec::new();

for item in stmts {
match item {
let others = stmts
.iter_mut()
.filter_map(|item| match item {
Stmt::Decl(Decl::Var(..)) => {
item.visit_mut_with(self);
None
}
Stmt::Decl(Decl::Fn(..)) => {
item.visit_mut_with(self);
None
}
_ => {
other_stmts.push(item);
}
}
}
_ => Some(item),
})
.collect::<Vec<_>>();

for other_stmt in other_stmts {
for other_stmt in others {
other_stmt.visit_mut_with(self);
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_transforms_optimization/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};

/// Assert in debug mode. This is noop in release build.
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn debug_assert_valid<N>(node: &N)
where
N: VisitWith<AssertValid>,
Expand Down
4 changes: 4 additions & 0 deletions crates/swc_ecma_usage_analyzer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! This crate is an internal crate that is extracted just to reduce compile
//! time. Do not use this crate directly, and this package does not follow
//! semver.
#![allow(clippy::mutable_key_type)]

pub mod alias;
Expand Down
Loading

0 comments on commit 76fe139

Please sign in to comment.