M+Minify++

Performance ยท profile first, keep same-run controls

Performance is a release gate now, not an after-the-fact chart.

Minify++ has three JavaScript cost profiles: a latency-sensitive conservative default, a structured scope-aware mode, and an aggressive bounded optimizer. Recent development made performance regressions an explicit rejection condition and added reproducible stage profiling plus same-run competitor controls.

The current rule

Correctness first; regressions still count.

Any real-bundle post-validation failure rejects a checkpoint. Aggregate throughput regressions above 2% are inspected, above 5% are normally rejected, and a regression above 10% on a large representative fixture is rejected unless a measured trade-off is deliberately accepted.

Historical GitHub milliseconds are not treated as a universal baseline. The exact same Minify++ revision measured materially differently across runner epochs, so current JavaScript comparisons refresh OXC, SWC, esbuild and Terser in the same benchmark job.

What the latest performance campaign changed

  • Reserved predictable syntax-analysis storage to avoid repeated container growth.
  • Indexed var/function redeclaration lookup inside scopes.
  • Propagated function ownership once instead of repeatedly walking scope ancestry.
  • Skipped semantic-fact construction in structured mode when that data is not consumed.
  • Avoided rebuilding an already-successful binding-renaming plan during later aggressive rounds.
  • Rejected a broad reference cache and a class-ancestry shortcut when they failed to produce safe representative wins.

The important result is the development process as much as the code: plausible optimizations that did not survive semantic or representative performance gates were removed rather than accumulated.

Real-bundle result

The external 12-artifact JavaScript benchmark validates conservative, structured and aggressive output independently. The current performance-certified checkpoint passed all 36 / 36 mode/artifact validations.

Against the preceding same-run-controlled baseline, environment-normalized median throughput improved by approximately 11.6% in default mode, 12.0% in structured mode and 20.1% in aggressive mode. These are comparative checkpoint figures, not portable speed promises.

DefaultLatency-sensitive path; competitive with the fastest native tools on several large bundles.
StructuredSpends more CPU for scope-aware safe binding renames and smaller JavaScript.
AggressiveBounded compression passes; materially more expensive than default but faster than Terser on the large fixtures where both complete in the current same-run suite.
ControlsOXC, SWC, esbuild and Terser are refreshed alongside Minify++ to expose runner drift.

Profile the real stages

make profile-js FILES="bundle.js" RUNS=7

MINIFY_PROFILE_STAGES=1 \
  .build/js-perf-profile --runs 3 --mode aggressive bundle.js

Stage attribution covers scanner/emission, concrete syntax construction, scope graph construction, reference resolution and semantic-fact construction. Profiling is opt-in and does not alter normal API output.

Internal per-format regression gate

make benchmark remains useful for format-level throughput, output size and RSS regression tracking. Those generated fixtures are direction-finding probes; the external 12-bundle JavaScript benchmark is the stronger acceptance oracle for structured/aggressive work.

Performance philosophy

  • Never trade semantic preservation for a favorable timing.
  • Compare old/new Minify++ with same-run controls when runner drift matters.
  • Keep raw samples and medians; do not certify from one timing sample.
  • Separate startup, throughput, memory and output size.
  • Retain an optimization only when representative workloads show a real win.