Skip to content

perf(es/minifier): Apply pending substitutions in batch for if-dense statement lists - #12079

Draft
Hamidreza Hanafi (hamidrezahanafi) wants to merge 1 commit into
swc-project:mainfrom
hamidrezahanafi:perf/minifier-batch-substitutions
Draft

perf(es/minifier): Apply pending substitutions in batch for if-dense statement lists#12079
Hamidreza Hanafi (hamidrezahanafi) wants to merge 1 commit into
swc-project:mainfrom
hamidrezahanafi:perf/minifier-batch-substitutions

Conversation

@hamidrezahanafi

@hamidrezahanafi Hamidreza Hanafi (hamidrezahanafi) commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description:

Profiling the minifier on large inputs showed that inline_with_multi_replacer is invoked tens of thousands of times per optimize() call (each call = one Finalizer walk + one NormalMultiReplacer walk over a small subtree), with hit rates of only 0.4–1.7% (replacer) and 0.006–0.04% (finalizer) — i.e. ~99% of the walks change nothing. About half of them came from merge_similar_ifs, which flushed pending substitutions into each if statement individually before merging (34k double walks per run on typescript.js alone).

This PR applies pending substitutions to the whole statement list in a single batch walk when the list is if-dense (>= 16 ifs), and keeps the per-if loop for sparse lists. The two forms are semantically equivalent: the substitution maps do not change during application except for consumption of applied entries, which happens in the same statement order either way. The density gate preserves the win where batching pays off (Σ(if subtrees) ≈ Σ(list)) without regressing if-sparse inputs, where per-if walks are already the cheaper strategy.

Output: byte-identical on the entire fixture corpus — the full suite passes with zero fixture updates required (including the big projects/bench lib fixtures).

Test plan:

  • cargo fmt --all
  • cargo clippy --all --all-targets -- -D warnings
  • cargo test -p swc_ecma_minifier (fully green, no fixture updates)
  • ./scripts/exec.sh (2128/2128 pass)

BREAKING CHANGE:

None.

Related issue (if exists):

#11517 (the pre-existing workaround this change preserves, but cheaper)

…statement lists

The optimizer flushed pending substitutions into each `if` statement
individually before `merge_similar_ifs` (one Finalizer + one
NormalMultiReplacer walk per `if`). On typescript.js this produced ~68k
double walks per optimize() call with 0.4-1.7% hit rates.

Apply the substitutions to the whole statement list in a single batch walk
when the list is if-dense (>= 16 ifs), and keep the per-if loop otherwise.
Both forms are semantically equivalent: the substitution maps only mutate
via consumption of applied entries, which happens in the same statement
order either way.

es/minifier/libs benchmarks: 2.5-13.8% faster across all 12 lib fixtures
(typescript -13.8%, echarts -11.8%, lodash -10.5%), byte-identical output
on the entire fixture corpus.
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 85c8c60

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codspeed-hq

codspeed-hq Bot commented Jul 28, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 200 untouched benchmarks
⏩ 61 skipped benchmarks1


Comparing hamidrezahanafi:perf/minifier-batch-substitutions (85c8c60) with main (e1877b4)2

Open in CodSpeed

Footnotes

  1. 61 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (0216889) during the generation of this report, so e1877b4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@hamidrezahanafi

Copy link
Copy Markdown
Contributor Author

Donny/강동윤 (@kdy1) I am testing a few optimizations with codespeed that actually move the needle when LTO is enabled. should there be someway to have LTO enabled for the benchmark? like adding a label to check LTO baseline?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant