From 69fe83657e8066041650de98941ac71b27517dad Mon Sep 17 00:00:00 2001 From: Alvin Wan Date: Sun, 5 Apr 2026 02:11:18 -0700 Subject: [PATCH 1/2] optimize speed and update benchmarks --- benchmarks/README.md | 18 +++++++++++++----- pymini/pymini.py | 16 +++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/benchmarks/README.md b/benchmarks/README.md index 858a300..cec5bec 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -16,13 +16,21 @@ Checked-in fixture comparison: | `tests/examples/pyminifier.py` | `1,355` bytes | `511` bytes, `62.3%` | `676` bytes, `50.1%` | `1,020` bytes, `24.7%` | | `tests/examples/pyminify.py` | `1,990` bytes | `981` bytes, `50.7%` | `1,605` bytes, `19.3%` | `983` bytes, `50.6%` | -TexSoup package validation: +TexSoup package mode (`pymini` only): | Input | Original | `pymini` | Reduction | | --- | ---: | ---: | ---: | | `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `33,107` bytes | `66.3%` | | `TexSoup/` compressed source (`.tar.gz`) | `70,532` bytes | `11,850` bytes | `83.2%` | +TexSoup file-by-file package comparison. All three outputs pass the upstream +TexSoup test suite (`78` tests): + +| Input | Original | `pymini` | `pyminifier` | `python-minifier` | +| --- | ---: | ---: | ---: | ---: | +| `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `32,131` bytes, `67.3%` | `34,643` bytes, `64.7%` | `83,303` bytes, `15.2%` | +| `TexSoup/` compressed source (`.tar.gz`) | `23,116` bytes | `10,926` bytes, `52.7%` | `9,741` bytes, `57.9%` | `21,532` bytes, `6.9%` | + # Speed Latency is machine-dependent. Recompute these with @@ -30,10 +38,10 @@ Latency is machine-dependent. Recompute these with | Input | `pymini` | `pyminifier` | `python-minifier` | | --- | ---: | ---: | ---: | -| `tests/examples/pyminifier.py` | `14.1 ms` | `0.4 ms` | `1.5 ms` | -| `tests/examples/pyminify.py` | `1227.6 ms` | `1.1 ms` | `4.0 ms` | -| `TexSoup/` package API | `4928.8 ms` | `—` | `—` | -| `TexSoup/` package CLI | `5062.0 ms` | `—` | `—` | +| `tests/examples/pyminifier.py` | `2.5 ms` | `0.4 ms` | `1.6 ms` | +| `tests/examples/pyminify.py` | `5.7 ms` | `1.2 ms` | `4.2 ms` | +| `TexSoup/` package API | `410.4 ms` | `—` | `—` | +| `TexSoup/` package CLI | `414.0 ms` | `—` | `—` | To reproduce those numbers: diff --git a/pymini/pymini.py b/pymini/pymini.py index 2ea7382..816a235 100644 --- a/pymini/pymini.py +++ b/pymini/pymini.py @@ -117,7 +117,7 @@ def visit(self, node): for child in ast.iter_child_nodes(node): child.parent = node self.visit(child) - return super().visit(node) + return node class CommentRemover(NodeTransformer): @@ -845,7 +845,6 @@ def transform(self, *trees): ) imported.transform(tree) append_public_aliases(tree, imported.nodes_to_append) - ParentSetter().visit(tree) new_trees.append(tree) return new_trees @@ -918,8 +917,6 @@ def transform(self, *trees): collector = RepeatedStringCollector() collector.visit(tree) RepeatedStringRewriter(self.generator, collector.repeated_strings_by_scope).visit(tree) - ParentSetter().visit(tree) - ast.fix_missing_locations(tree) return trees @@ -1080,10 +1077,7 @@ def __init__(self, generator): def transform(self, *trees): for tree in trees: - ParentSetter().visit(tree) self.visit(tree) - ParentSetter().visit(tree) - ast.fix_missing_locations(tree) return trees def _next_safe_name(self, reserved_names): @@ -1352,6 +1346,13 @@ def transform(self, *trees): yield ast.unparse(tree) +class LocationFixer(Transformer): + def transform(self, *trees): + for tree in trees: + ast.fix_missing_locations(tree) + return trees + + def module_prefixes(module: Optional[str]) -> List[str]: if not module: return [] @@ -1727,6 +1728,7 @@ def minify(sources, modules='main', keep_module_names=False, ), # final post-processing to remove whitespace (minify) + LocationFixer(), Unparser(), WhitespaceRemover(), ) From ddcb418774ed046e6d0c1a8a742195f43963f17e Mon Sep 17 00:00:00 2001 From: Alvin Wan Date: Sun, 5 Apr 2026 02:23:09 -0700 Subject: [PATCH 2/2] refresh benchmark docs --- README.md | 6 ++--- benchmarks/README.md | 53 +++++++++++++------------------------------- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index ddef45d..4bb8462 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ `pymini` is an AST-based Python minifier for scripts and packages. It preserves package layout by default, can emit a single-file bundle when asked, and can -shrink Python code by roughly `30%` to `90%` depending on the codebase and -whether you compare raw source or compressed archives. +shrink Python code by roughly `15%` to `70%` on the checked-in fixtures and +validated package benchmarks. - [Getting Started](#getting-started) - [Installation](#installation) @@ -46,7 +46,7 @@ Current checked-in fixtures: | `tests/examples/pyminifier.py` | `1,355` bytes | `511` bytes | `62.3%` | | `tests/examples/pyminify.py` | `1,990` bytes | `981` bytes | `50.7%` | | `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `33,107` bytes | `66.3%` | -| `TexSoup/` compressed source (`.tar.gz`) | `70,532` bytes | `11,850` bytes | `83.2%` | +| `TexSoup/` compressed source (`.tar.gz`) | `23,118` bytes | `11,368` bytes | `50.8%` | For baseline comparisons, speed results, and TexSoup validation details, see [benchmarks/README.md](./benchmarks/README.md). diff --git a/benchmarks/README.md b/benchmarks/README.md index cec5bec..dbbe726 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -3,47 +3,26 @@ This directory holds the current size and speed measurements for `pymini`, plus the benchmark harness used to reproduce them. -- [Compression](#compression) -- [Speed](#speed) +- [Results](#results) +- [Reproduce](#reproduce) - [TexSoup Validation](#texsoup-validation) -# Compression +# Results -Checked-in fixture comparison: +| Input | Original | `pymini` size | `pymini` speed | `pyminifier` size | `pyminifier` speed | `python-minifier` size | `python-minifier` speed | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| `pyminifier.py` | `1,355` bytes | `511` bytes, `62.3%` | `2.4 ms` | `676` bytes, `50.1%` | `0.4 ms` | `1,020` bytes, `24.7%` | `1.6 ms` | +| `pyminify.py` | `1,990` bytes | `981` bytes, `50.7%` | `5.4 ms` | `1,605` bytes, `19.3%` | `1.2 ms` | `983` bytes, `50.6%` | `4.1 ms` | +| `TexSoup/*.py` | `98,181` bytes | `33,107` bytes, `66.3%` | `399.3 ms` | `34,643` bytes, `64.7%` | `31.3 ms` | `83,303` bytes, `15.2%` | `120.8 ms` | +| `TexSoup.tar.gz` | `23,118` bytes | `11,368` bytes, `50.8%` | `—` | `9,741` bytes, `57.9%` | `—` | `21,532` bytes, `6.9%` | `—` | -| Input | Original | `pymini` | `pyminifier` | `python-minifier` | -| --- | ---: | ---: | ---: | ---: | -| `tests/examples/pyminifier.py` | `1,355` bytes | `511` bytes, `62.3%` | `676` bytes, `50.1%` | `1,020` bytes, `24.7%` | -| `tests/examples/pyminify.py` | `1,990` bytes | `981` bytes, `50.7%` | `1,605` bytes, `19.3%` | `983` bytes, `50.6%` | +`TexSoup/*.py` compares validated package outputs. `pymini` uses package mode; +the baselines minify each file independently in the preserved package tree. All +three outputs pass the upstream TexSoup test suite (`78` tests). -TexSoup package mode (`pymini` only): +# Reproduce -| Input | Original | `pymini` | Reduction | -| --- | ---: | ---: | ---: | -| `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `33,107` bytes | `66.3%` | -| `TexSoup/` compressed source (`.tar.gz`) | `70,532` bytes | `11,850` bytes | `83.2%` | - -TexSoup file-by-file package comparison. All three outputs pass the upstream -TexSoup test suite (`78` tests): - -| Input | Original | `pymini` | `pyminifier` | `python-minifier` | -| --- | ---: | ---: | ---: | ---: | -| `TexSoup/` raw Python source (`*.py`) | `98,181` bytes | `32,131` bytes, `67.3%` | `34,643` bytes, `64.7%` | `83,303` bytes, `15.2%` | -| `TexSoup/` compressed source (`.tar.gz`) | `23,116` bytes | `10,926` bytes, `52.7%` | `9,741` bytes, `57.9%` | `21,532` bytes, `6.9%` | - -# Speed - -Latency is machine-dependent. Recompute these with -`PYTHONPATH=. .venv/bin/python benchmarks/benchmark_speed.py`. - -| Input | `pymini` | `pyminifier` | `python-minifier` | -| --- | ---: | ---: | ---: | -| `tests/examples/pyminifier.py` | `2.5 ms` | `0.4 ms` | `1.6 ms` | -| `tests/examples/pyminify.py` | `5.7 ms` | `1.2 ms` | `4.2 ms` | -| `TexSoup/` package API | `410.4 ms` | `—` | `—` | -| `TexSoup/` package CLI | `414.0 ms` | `—` | `—` | - -To reproduce those numbers: +Recompute the speed measurements with: ```bash python3 -m pip install -e ".[dev]" python-minifier @@ -56,9 +35,9 @@ PYTHONPATH=. .venv/bin/python benchmarks/benchmark_speed.py --pyminifier-root /t `pymini` has been validated against the upstream `TexSoup` test suite in package mode. Current validation: upstream pytest passes (`78` tests), raw source code is `66.3%` smaller, and compressed source code (`.tar.gz`) is -`83.2%` smaller. +`50.8%` smaller when measured on clean `.py`-only package snapshots. - + To reproduce that flow locally: