Performance¶
For: developers sizing up the library or tuning large-lot workloads.
The wafermap library is fast. Rendering the map is effectively instant, and every optional analysis feature — the Summary panel's statistics, the Insights tab's charts, automatic spatial finding detection — completes in single-digit to low double-digit milliseconds for a typical wafer. This page exists so you can pick features by what they do, not out of worry about what they cost — but if you're curious, or you're working with very large lots, here are the actual numbers.
How to read the numbers below: timings were measured on ordinary machines, not tuned or server-grade hardware: the analysis tables on a ThinkPad T14s Gen 2i laptop (Core i5-1145G7, Node 22), the Insights table on a 3-year-old SER5-Pro mini-desktop, both running Ubuntu. Expect similar or better numbers on a typical dev machine or a user's browser. Percentages are included alongside the milliseconds purely as a size comparison between features, not as a warning sign — a feature going from 4ms to 9ms is still 9ms.
The short version¶
Every optional feature costs low single-digit to low double-digit
milliseconds per wafer — the kind of cost that's invisible to a user, even
on a full production-density wafer. There's exactly one feature worth
knowing about before you flip it on for every wafer in a large lot:
automatic spatial finding detection (enableTestValueAnalysis), which is
the most powerful option (it finds statistical anomalies for you) and also
the priciest — about 30ms on the largest wafer we tested, but it scales with
the whole lot. Its default, 'auto', runs it only within a budget and offers
it to the reader above that (see "Deciding for the user"). Everything
else — the Summary panel, distribution charts, all of the Insights tab — is
cheap enough to enable freely. Two Insights panels even get faster for
free if the Summary panel already ran first.
Download size¶
The one place these figures are stated. Everything else in the docs links here, because a number repeated in six places is a number that will be wrong in five of them.
| Entry point | gzipped | When it is downloaded |
|---|---|---|
@wafertools/wafermap — the data and stats layer, no DOM |
~85 KB | Always, if you import it |
@wafertools/wafermap/render — the interactive renderer |
~190 KB | Always, if you render |
| Insights chart suite | +~115 KB | On first open, only if insights is left on (the default) |
| In-app user guide | +~70 KB | On first open of the guide |
| Summary report builders | +~10 KB | When a report is opened from the Summary panel or the gallery |
Two things worth reading off that table:
- The data layer runs without a DOM, so a Node pipeline that builds and analyses wafer maps without drawing them pays only the first row, not the renderer's.
- The optional pieces are lazy. A page that renders maps but never opens
Insights, the guide or a report never downloads those last three rows. They are separate chunks, fetched on
first use — any bundler with dynamic
import()splitting (Vite, Rollup, webpack, esbuild withsplitting: true) does this by default.
There are no runtime dependencies, so these figures are the whole cost — there is no transitive tree underneath them.
These figures are generated, not typed. npm version runs
scripts/check-bundle-size.mjs --write, which bundles the built dist/ with esbuild and
real code-splitting, gzips each chunk, and rewrites the numbers above — and the README's
badge and prose line, the only other place any of them appear. Don't hand-edit them; a
release will overwrite the edit.
Between releases the same script runs as a check (npm run check, npm test) and fails
if any quoted figure has drifted, so a change that moves the bundle is caught at the commit
that causes it rather than at the next release. WMAP_CHUNKS=1 node
scripts/check-bundle-size.mjs prints the per-chunk breakdown.
What each option actually buys you¶
| Option | What you get | Where it shows up |
|---|---|---|
(none — just buildWaferMap + renderWaferMap) |
The interactive map itself | Always |
analyzeWaferMap({ enableTestValueAnalysis: false }) |
Yield %, bin breakdown, ring/quadrant yield (stats.regionYield), pass rates by test limits, tester verdict and functional test, basic findings |
Summary panel, Insights → Overview |
analyzeWaferMap({ computePerTestStats: true }) |
Per-test five-number summaries (min/Q1/median/Q3/max) and Cp/Cpk/Pp/Ppk (stats.capability) |
Insights → Distributions |
analyzeWaferMap() — the default, 'auto' |
Automatic spatial statistical findings — flags regions where a test's values differ significantly (Welch's t-test per region) — when within the budget; otherwise offered in the Findings section. true runs it at any size |
Summary panel's findings list |
| Insights tab (on by default) | Process capability, distributions, correlation charts | Insights tab (the Maps |
analyzeWaferLot(..., { perWaferSummaries }) |
Lot-level findings + reuses per-wafer stats you already computed | Gallery's lot Summary panel |
computePerTestStats and the test-value analysis are alternatives, not
a ladder: the test-value analysis computes distributions internally too, so
you don't need both. With the default enableTestValueAnalysis: 'auto' the
library runs it within its budget; pass false and computePerTestStats: true
if you want only the distributions.
Measured cost, by wafer size¶
Full 300mm wafers, 15 parametric tests per die — a realistic count for analysing
a subset of DC/AC parametrics from a production test program, not a toy
single-test example. buildWaferMap (drawing the map) is included as a
reference point — it's mandatory, everything else is opt-in on top of it.
| Wafer size | buildWaferMap |
analyzeWaferMap, test values off (Summary panel) |
+ computePerTestStats |
+ test-value analysis |
|---|---|---|---|---|
| Small (~150 dies) | 2.2ms | 2.5ms (+113%) | 2.7ms (+122%) | 5.2ms (+232%) |
| Medium (~1,000 dies) | 9.9ms | 4.5ms (+45%) | 6.0ms (+61%) | 9.7ms (+98%) |
| Large (~4,300 dies) | 37.7ms | 13.5ms (+36%) | 19.7ms (+52%) | 31.4ms (+83%) |
(Each analysis column is the analyzeWaferMap call alone, with its cost as a
percentage of buildWaferMap in brackets — included for scale comparison, not
as a target to avoid.)
Every number here is small enough to not think about — low double-digit
milliseconds even at 4,300 dies × 15 tests. enableTestValueAnalysis is the
one to be deliberate about: it compares every region against the rest of the
wafer for every test, so it scales with both die count and test count, and
on the largest, densest wafer with 15 tests it reaches ~30ms. Still fast for a one-off computation — just not
something you'd want re-running on every frame of an animation, and worth
knowing it grows with how many tests you hand it (more on that below).
The number that matters is the lot, not the wafer¶
The table above is per wafer, and that is not the unit anyone decides at. A
host runs analysis over a whole lot, so the figure a user waits for is that cost
multiplied by the wafer count — and enableTestValueAnalysis is the one column
where the multiplication takes you somewhere different in kind, not just in
degree. "30ms, still fast" becomes seconds on a large lot without anything about the
option changing.
Measured over whole lots, on synthetic wafers with a real edge effect so the findings pass is doing genuine work (Node, median of 3):
| Lot | analysis without | with enableTestValueAnalysis |
added |
|---|---|---|---|
| 5 wafers × 10.5k dies × 30 tests | 172ms | 629ms | +457ms |
| 5 × 10.5k × 100 tests | 225ms | 1.6s | +1.3s |
| 25 × 10.5k × 30 tests | 684ms | 3.0s | +2.3s |
The added cost is a linear scan, so one coefficient spans these rows: about 0.3µs per (wafer × die × test) in Node and Chrome. The engine matters here: WebKit — Safari, and the desktop webviews on Linux and macOS — runs it at about 0.65µs, measured on a real 25-wafer, 266k-die, 51-test lot. That is enough to predict "instant or not" before you run it, which is the only question you actually need answered. Size the estimate for the slowest engine your users have:
Calibrate the coefficient on your own target hardware if you are going to branch on it, and round it up rather than down — the cost of over-estimating is one unnecessary prompt, and the cost of under-estimating is an unexplained freeze.
Deciding for the user, and when not to¶
enableTestValueAnalysis used to leave integrators with two bad options: leave
it off (safe, but the findings are then invisible and most users never learn
they exist) or turn it on for everybody and make large lots feel broken.
The library now decides, the same way in every app. The default, 'auto',
runs it when it is cheap and offers it when it is not: up to 1,500,000 test
values (dies × tests, summed over a lot's wafers, about a second in the slower
engine) it runs; above that it is skipped, the summary records it
(stats.testValueAnalysis, with the size and an estimated time), and the
renderers' Findings section says so with an Analyse button. The offer
appears where the reader is already looking rather than in a menu they have no
reason to open, and it states the price, because an unpriced button on a lot
where the answer takes half a minute is worse than no button.
const lot = await analyzeWaferLotAsync(results); // one decision for the whole lot
renderWaferGallery(container, items, {
lotStatsSummary: lot,
// The reader pressed Analyse: the gallery re-ran the lot and already shows it.
onStatsSummaryChange: ({ summaries, lotStatsSummary }) => keep(summaries, lotStatsSummary),
});
Pass true to run it whatever the size, or false to leave it (and the
limit-failure region findings) out.
Only pass the tests you'll actually use¶
testDefs should be the tests you intend to analyze or display — not
necessarily every test in the underlying test program. A few features scale
with test count, and one of them (test correlation) scales quadratically,
since it compares every test against every other test:
| Tests passed | computePerTestStats |
enableTestValueAnalysis |
Insights test correlation (pairs) |
|---|---|---|---|
| 6 | 4.7ms | 5.5ms | 0.3ms (15 pairs) |
| 15 | 5.1ms | 7.9ms | 1.3ms (105 pairs) |
| 30 | 5.8ms | 12.4ms | 4.4ms (435 pairs) |
| 60 | 8.2ms | 16.4ms | 17.0ms (1,770 pairs) |
(medium/~1,000-die wafer, held constant — only the test count changes)
None of this is expensive at reasonable test counts (15–30 is typical), but
if your data source hands you a full parametric test program with hundreds
of tests and only a handful are ever shown to the user, filter testDefs
down to the ones you actually chart or analyze before passing it in — the
library has no way to know which tests you care about, so it treats every
testDef you give it as one worth analyzing.
The Insights tab: fast alone, faster still if the Summary panel already ran¶
Every Insights panel computes in single-digit milliseconds, even on the largest wafer tested (the priciest panel, process capability, tops out around 21ms at 4,200 dies × 15 tests). Two panels — box plot and bin pareto — get even cheaper if the Summary panel already computed its stats, since they can reuse that work instead of re-scanning every die:
| Panel | Computed standalone | Reusing the Summary panel's stats |
|---|---|---|
| Box plot | up to ~1ms | ~2µs |
| Bin pareto | up to ~175µs | ~2µs |
| Capability | up to ~21ms | shared — the pooled per-test pass is computed once per population and reused |
| Correlation, scatter, histogram | up to ~21ms | (always reads raw die values — no reuse path, by design) |
Practical effect: if a user opens the Summary panel first and then the Insights tab, box plot and bin pareto are effectively free. If Insights is the first thing they open, everything computes fresh — still fast, just not quite as fast.
The per-test pass over a lot's pooled dies is computed once, whoever asks for it first: the lot Summary panel, the Insights Overview and the capability chart share one computation instead of running three. It is memoised on the population it describes, so any change to that population recomputes it — there is no signature of "what it depends on" to get subtly wrong.
Returning to an Insights tab you have already opened is instant. A built section is kept per view, so going back to Distributions does not re-run capability, boxplot, histogram and trend (about 5s at 400,000 dies). Only a pure tab switch reuses it — new data, Group by, scope or an axis preference rebuilds everything.
Correlation reads at most 25,000 dies. It is the one analysis that is
quadratic in tests and linear in dies, so above that budget it samples by an
even stride across the whole population rather than running for minutes. The
coefficients are unchanged at the precision they are shown to (the standard error
of r at n=25,000 is under 0.007), and a sampled matrix always says so on the
panel and in CorrelationMatrix.sample.
And at most 300 tests, and 2.5×10⁸ pair updates. Pairs grow with the square of
the test count: 2,002 tests took 38 s and 15,000 never finished. Above 300 parametric
tests, the ones paired are chosen first — most dies outside their limits, then the widest
spread against the limit window — and the panel names the selection
(CorrelationMatrix.testSample). Past 2.5×10⁸ pair updates (300 tests over more than
~5,600 dies) the dies are thinned to fit, evenly, which the sample note reports; the
standard error of r there is about 0.013.
Galleries: let analyzeWaferLot reuse work you already did¶
If you're building a gallery and computing statsSummary for each card
and a lot-level lotStatsSummary, pass the per-wafer results you already
have — it's a free win:
const waferSummaries = results.map(r => analyzeWaferMap(r));
const lotSummary = analyzeWaferLot(results, {
perWaferSummaries: waferSummaries, // ← reuse, don't let it recompute
});
Without perWaferSummaries, analyzeWaferLot quietly redoes the entire
per-wafer analysis itself internally — for a 6-wafer lot, that's roughly six
times the per-wafer analyzeWaferMap cost from the table above, paid a
second time for no benefit. With perWaferSummaries supplied, the lot-level
pass adds no measurable time on top of the per-wafer work you already did:
lot capability, pass rates and region yield are pooled from the wafer summaries
rather than recomputed from the dies (well under a millisecond for 25 wafers of
4,000 dies and 15 tests).
Either way the absolute cost is small at typical lot sizes — this is a free
optimization worth taking, not a fix for a real bottleneck.
Recommended settings by what you're building¶
| You're building… | Recommended options | Why |
|---|---|---|
| A simple embedded viewer, no stats UI | Nothing extra — just buildWaferMap + renderWaferMap |
No reason to compute analysis nobody sees |
| A viewer with yield/bin summary | analyzeWaferMap(), no extra flags |
Fast, powers the whole Summary panel |
| …plus distribution/box-plot charts | + computePerTestStats: true |
Still fast; unlocks Insights → Distributions |
| A QA/engineering tool that should flag anomalies automatically | The default ('auto'), with analyzeWaferLotAsync for lots |
The only option that finds spatial patterns for you automatically. Cheap per wafer, seconds per lot, so the library decides per lot and offers it above the budget (see "The number that matters is the lot") |
| A lot gallery (many wafers) | Always pass perWaferSummaries to analyzeWaferLot |
Free reuse of work you already did |
| A lot gallery large enough to stage visibly | Pass factories, and hold your own indicator to onItemsResolved |
The cards stream in one per task instead of blocking; onItemResolved sizes and advances a bar, and the Summary panel keeps filling in for seconds after the last card (guide) |
Very large lots using enableTestValueAnalysis on every wafer |
Run analysis in a Web Worker via createWafermapWorker |
Keeps the main thread free while the heavier pass runs, even though each individual call is fast |
| A dashboard rendering many wafers/lots at once | Compute statsSummary for every card, then open Insights on demand |
Box plot and bin pareto ride along nearly free once Summary panel stats exist |
| Any app, if your data source has a large parametric test program | Filter testDefs down to the tests you actually analyze/display |
Correlation and enableTestValueAnalysis both scale with test count — unused tests cost you for nothing shown to the user |
Methodology, if you want to reproduce this¶
Wafer sizes above are full 300mm wafers with realistic die pitch (small:
~20mm die, medium: ~8mm die, large: ~4mm die), 15 parametric tests per die
(the test-count table varies this deliberately), 8% fail rate, deterministic
synthetic data. Each number is the median
of many repeated runs (9–41, more reps for smaller/faster operations) to
cancel out timer jitter and GC pauses — a single-shot timing at this scale
isn't trustworthy. Every function measured is a real, unmodified library
export, run against the actual built dist/ output in Node 22: the analysis
and test-count tables on a ThinkPad T14s Gen 2i (Core i5-1145G7), the Insights
table on a 3-year-old SER5-Pro desktop, both on Ubuntu — nothing exotic, and a
typical modern machine should do at least as well. The WebKit coefficient comes
from the same lot run in WebKitGTK, the engine of the desktop webviews on
Linux.