Repository navigation
test(@angular/build): add library builder scaling benchmark suite #34290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
dherges
wants to merge
8
commits into
angular:main
Choose a base branch
from
dherges:test-ng-build-library-builder-benchmarks
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+714
−2
Draft
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
df5dc1a
test(@angular/build): add library builder scaling benchmark suite
dherges 19f942c
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges 136c57a
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges 0111149
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges 90b8b5f
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges 25e62c9
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges c822f4f
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges a39f001
fixup! test(@angular/build): add library builder scaling benchmark suite
dherges File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| # Library Builds Scaling Benchmark | ||
|
|
||
| This benchmark suites measures build time metrics for Angular libraries. | ||
|
|
||
| ## Context | ||
|
|
||
| Angular CLI is in the process of transitioning from `ng-packagr` to Angular CLI's native library builder. | ||
| Both library build systems are registered Architect builders: | ||
|
|
||
| - `@angular/build:ng-packagr`: runs library builds through the legacy `ng-packagr` build pipeline. | ||
| - `@angular/build:library`: runs library builds through Angular CLI's native library builder. | ||
|
|
||
| This benchmark suite is designed to quantify performance impacts and ensure scalability. | ||
|
|
||
| ### Previous Benchmarks | ||
|
|
||
| For context, when running a comparable benchmark suite directly against `ng-packagr`, it hinted at a "U-curve" in build times, | ||
| where build time per entry point in `ng-packagr` drops until entry point N (with N varying in the range 200..500). | ||
| This shape indicates an initial overhead as a possible explanation for a small number of entry points. | ||
| However, we discovered that `ng-packagr` build time per entry point increases | ||
| as the number of entry points scale past a certain N (in the test runs N=200+). | ||
|
|
||
| [Landing Angular CLI's native `@angular/build:library` builder](https://github.com/angular/angular-cli/pull/34106#issuecomment-5744311681), | ||
| indicated very favourable performance metrics for the new library builder that can be explained by a number of | ||
| architectural changes in the design of the library build pipeline: | ||
|
|
||
| - Shared Stylesheet Bundling | ||
| - Public API Invalidation via Declaration Hashing | ||
| - Dedicated Worker Pool for Production Builds | ||
| - Single Pass Output Emission | ||
| - Direct Async Pipeline | ||
|
|
||
| The benchmark design of this suite is built to not only verify the previous results, | ||
| but also to confirm or contradict the architectural choices in the library builder pipeline. | ||
|
|
||
| ### Goals | ||
|
|
||
| This benchmark suite runs explorative research on the performance of library builds in Angular CLI. | ||
| Interesting results from the previous benchmarks indicate 2 measurable goals: | ||
|
|
||
| - Measure the new library builder against itself: How does it scale with the number of entry points? | ||
| - Enable A/B benchmarks of the `@angular/build:ng-packagr` builder against `@angular/build:library` builder: How much do library builds benefit from the transition to the native library builder? | ||
|
|
||
| As the previous benchmark runs hint at significant performance improvements in the new library builder, | ||
| this benchmark suite aims to quantify the impact of that transition. | ||
|
|
||
| ## Benchmark Design | ||
|
|
||
| This suite supports two primary layouts to test different filesystem and entry-point scaling scenarios: | ||
|
|
||
| - **Flat (`--layout=flat`)**: Generates $N$ secondary entry points as direct siblings, where $N$ | ||
| comes straight from `--count`. | ||
| - Design: Each entry point is named `comp-i` (e.g., `comp-0001`, `comp-0002`). | ||
|
|
||
| - **Deep (`--layout=deep`)**: Generates $2^N$ secondary entry points in a binary tree structure. | ||
| - Design: Creates a nested directory structure (`sub-a`/`sub-b` at each level). | ||
| - Depth $N$ is not an independent sweep parameter -- see "Sweep rounds" below. | ||
|
|
||
| Each entry point consists of a `public-api.ts` and a component file with an inline template | ||
| containing a bit of per-entry content derived from the entry's id (e.g. `<h2>Component no. | ||
| 0042</h2>`), so entries aren't byte-identical copies of each other. | ||
|
|
||
| ### Sweep rounds | ||
|
|
||
| `--count` is the one sweep parameter: a comma-separated list of entry-point count targets. The | ||
| default sweep spans small to large entry-point counts in one round, chosen to resolve the | ||
| U-shaped scaling pattern observed with `ng-packagr` (see above): | ||
| `10, 20, 50, 100, 200, 300, 400, 500, 1000, 2000`. | ||
|
|
||
| For `--layout=deep`, there's no "number of entry points" parameter directly -- a binary tree of | ||
| depth $N$ always has exactly $2^N$ leaves/entry points. So `--count`'s targets are translated into | ||
| tree depths: for each target `n`, the smallest `depth` with `2^depth >= n`, de-duplicated (e.g. | ||
| `300`, `400`, and `500` all round up to the same `depth=9`, i.e. 512 entry points). This keeps | ||
| both layouts comparable at roughly the same entry-point scale without exposing depth as a second, | ||
| independently-tunable sweep axis. `--depth` still exists as an **explicit override** for | ||
| `--layout=deep` -- pass it directly to sweep exact tree depths instead of entry-point targets -- | ||
| but it is not part of the default sweep design and is ignored for `--layout=flat`. | ||
|
|
||
| ## Running the Benchmarks | ||
|
|
||
| ```sh | ||
| # Ensure the builder is built first | ||
| pnpm build | ||
|
|
||
| # Run the benchmark | ||
| pnpm admin benchmark library-builder [options] | ||
| ``` | ||
|
|
||
| ### Options | ||
|
|
||
| | Option | Description | Default | | ||
| | :---------------------- | :--------------------------------------------------------------------------------------------------- | :--------------------------------------- | | ||
| | `--layout=<flat\|deep>` | Fixture layout | `flat` | | ||
| | `--count=<n,n,...>` | Entry-point count targets, drives both layouts | `10,20,50,100,200,300,400,500,1000,2000` | | ||
| | `--depth=<n,n,...>` | **`layout=deep` only.** Explicit tree depths, overriding the depths otherwise derived from `--count` | derived from `--count` | | ||
| | `--iterations=<n>` | Measured iterations per size | `3` | | ||
| | `--json` | Output raw measurements as JSON | N/A | | ||
|
|
||
| > `--count` and `--depth` are not independent sweep axes for `layout=deep`: `--count` is translated | ||
| > into depths (smallest `depth` with `2^depth >= n` per target, de-duplicated); `--depth` only | ||
| > matters when you want to bypass that translation and name exact tree depths yourself. Passing | ||
| > both for `layout=deep` makes `--depth` win; `--depth` is ignored entirely for `layout=flat`. | ||
|
|
||
| ### Example | ||
|
|
||
| Sweep the flat layout at a single size: | ||
|
|
||
| ```sh | ||
| pnpm admin benchmark library-builder --layout=flat --count=500 | ||
| ``` | ||
|
|
||
| ## Benchmark Results | ||
|
|
||
| Each entry below is one benchmark run, with its own summary and conclusions. | ||
|
|
||
| ### 2026-10-12: Iteration 1 baseline sweep (native library builder, flat & deep) | ||
|
|
||
| First full run of the default sweep against `@angular/build:library` only — no `ng-packagr` A/B comparison yet (still Future Work). | ||
| Fixtures use inline HTML templates only, no stylesheets. | ||
| Default `--count` sweep, `--iterations=3`, both layouts. | ||
| Environment: 4-core / 16 GiB sandbox, NVMe SSD. | ||
| Raw log: `tmp/benchmark-run-1791795379.log` (gitignored; not included in this commit). | ||
|
|
||
| #### Flat layout (inline templates only) | ||
|
|
||
| | Entries | Median | ms/entry | | ||
| | ------: | -----: | -------: | | ||
| | 10 | 3.25s | 324.63 | | ||
| | 20 | 3.28s | 164.14 | | ||
| | 50 | 3.33s | 66.61 | | ||
| | 100 | 3.75s | 37.53 | | ||
| | 200 | 4.31s | 21.56 | | ||
| | 300 | 4.90s | 16.35 | | ||
| | 400 | 5.51s | 13.78 | | ||
| | 500 | 6.18s | 12.36 | | ||
| | 1000 | 10.29s | 10.29 | | ||
| | 2000 | 19.47s | 9.74 | | ||
|
|
||
| #### Deep layout (inline templates only) | ||
|
|
||
| | Entries | Median | ms/entry | | ||
| | ------: | -----: | -------: | | ||
| | 16 | 3.27s | 204.47 | | ||
| | 32 | 3.30s | 103.16 | | ||
| | 64 | 3.57s | 55.74 | | ||
| | 128 | 4.20s | 32.84 | | ||
| | 256 | 4.70s | 18.36 | | ||
| | 512 | 6.31s | 12.32 | | ||
| | 1024 | 10.25s | 10.01 | | ||
| | 2048 | 21.67s | 10.58 | | ||
|
|
||
| **Conclusion:** per-entry build time decreases monotonically across both layouts and flattens out | ||
| around 9.6-10.6ms/entry at the largest sizes tested -- no U-shaped climb-back-up at any tested | ||
| size (up to 2000/2048 entries), unlike the pattern reported for `ng-packagr` past N≈200-500. Flat | ||
| and deep track each other closely at comparable entry-point scale, suggesting the directory | ||
| layout itself isn't a meaningful cost driver for the native builder, just raw entry-point count. | ||
| `deep`'s depth-11 run (2048 entries) showed slightly higher run-to-run variance (20.78s-22.46s) | ||
| than `flat`'s 2000-entry run (19.46s-19.63s) -- possibly directory-tree-walk overhead at depth, | ||
| but not a regression, and not yet investigated further. | ||
|
|
||
| All 60 builds (10 sizes x 3 iterations x 2 layouts) completed with `status: "ok"`; zero failures. | ||
|
Comment on lines
+124
to
+161
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Iterated the Benchmark Design and ran the first full default sweep against
|
||
|
|
||
| ## Future Work | ||
|
|
||
| - Style-flavor matrix: inline / inline-css / inline-scss / external / external-css / external-scss, swept across both layouts. | ||
| - A/B comparison against `ng-packagr` (both are already registered Architect builders — `@angular/build:ng-packagr` and `@angular/build:library` — so this is a same-builder-package comparison, not a separate tool invocation). | ||
| - Dependencies between entry points. | ||
| - Shared stylesheet tokens imported by multiple components. | ||
| - Watch mode. | ||
| - Peak memory (`ru_maxrss`) and CPU time (`ru_utime` + `ru_stime`) metrics per build, in addition to wall-clock build time. | ||
| - Library "taste" variants beyond plain flat/deep, e.g. flat/deep with inter-entry-point dependencies or shared SCSS tokens (currently out of scope, listed above). | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.