Performance

What shipped, measured. Every figure on this page comes from a seeded benchmark: the three in the repository, scripts/bench-solver.js, scripts/bench-search.js and scripts/bench-tools.js, and the same harness pointed at the newest engine families. Seeded instances, answers checked against independently computed ground truth, re-measured on every engine change. Naming those three files is not the same as handing them to you, so say the awkward part plainly: the repository is private, so you cannot download them and re-run this page for yourself today. What you can check is the answer rather than the clock, because every run is seeded and a seed reproduces a run to the last digit on your machine as well as ours. The engine is also packaged as a plain Node library that runs off a spreadsheet, and if you want it, or the benchmark harness, before it is public, the developer page says how to ask. Last measured 2 September 2026; the engine families that shipped since, schedule risk over a task network, tree sensitivity and information, ETS and ARIMA forecasting and the efficient frontier, were measured 5 September 2026 with the same conventions: warm-up runs excluded, the median of repeated runs.

The ceilings, per engine

Every limit is a number the engine was measured at, not a marketing tier. Pro raises none of them: the ceilings are the engine's own, and they are the same on every plan. When your model reaches across sheets or uses a function the in-browser engine does not implement, the run falls back to recalculating the sheet itself, and the panel says so before it writes anything; the fallback limits are lower because every step of that search is a round trip to Google.

What a ceiling counts. A decision cell is a cell the engine is allowed to change: the unknowns it solves for, all at once, where every one interacts with every other. That is the number the ceilings below count, and it is not the size of your spreadsheet. A sheet holding 200,000 cells of data that feed 40 changing cells is a 40-cell model, comfortably inside every limit on this page. Reading data is a separate and much larger number: one selection can carry up to 500,000 cells into the analysis tools. Monte Carlo trials are a third axis again, and run to 100,000,000. The ceilings sit where the worst case, a fully dense model in which every constraint touches every cell, crosses about a second of solve time in the browser tab; real models are usually far sparser than that worst case and solve well under it. Past the dense window a separate sparse engine takes over, up to 10,000 cells and 3,000 constraints: it is built for models where each constraint touches only a few cells, every answer it returns is re-checked by an independent verifier before anything is written, and because its speed genuinely depends on that sparseness, the panel says before the run whether your model is the seconds kind or the minutes kind.

EngineIn the browserSheet fallback
Simplex LP, continuous2,000 decision cells, 600 constraints, answered in about a second; up to 5,000 cells and 1,200 constraints with a progress bar (densest shape measured at 16 s); up to 10,000 cells and 3,000 constraints on the sparse engine with a progress bar, measured at 22.7 s at that full size when each constraint touches only a few cells, while a densely coupled model that size can take minutes, and the panel says which to expect before it runs200 cells
Simplex LP, whole numbers500 integer cells, optimum proven200 (the sheet cell cap binds first)
Evolutionary100 cells, 30 integer15
Nonlinear60 cells10
Monte Carlo100,000,000 trials (10,000 default)time-budgeted

The engine families that shipped after that table was written have ceilings of their own, and the same rule holds: every plan gets the same ceiling, and each one was measured, on 5 September 2026, at the sizes in the tables further down.

EngineThe ceiling
Schedule risk over a task network50,000 trials (5,000 default), inside a 210-second budget the engine checks every 256 trials; an early stop keeps every trial already run
Tree sensitivity and informationthe tree arrives through the same 500,000-cell selection gate as the analysis tools; the probability sweep runs 11 to 101 points, 41 by default
ETS forecasting8 to 50,000 readings, horizon up to 120; the auto scan compares an 8-model family and names the winner
ARIMA forecasting16 to 20,000 readings, with at least 12 surviving differencing; horizon up to 120; differencing 0 to 2 chosen by a stationarity test, orders 0 to 3 each way, and the stepwise search fits at most 24 candidates
Efficient frontier, risk-aware optimization2 to 25 points; each point re-runs the optimizer in full, 200 trials per candidate by default, inside a 55-second compute budget

Measured: optimization

Linear programs, solved and proven optimal in the browser:

ModelResultTime
100 cells × 40 constraintsproven optimal0.2 ms
200 cells × 80 constraintsproven optimal0.5 ms
500 cells × 200 constraintsproven optimal1.7 ms
1,000 cells × 400 constraintsproven optimal5.4 ms

Whole-number decisions are the hard case: the instances below are 0/1 selection problems chosen because they force the search to actually branch. Proven means the engine finished the search and the answer is the optimum, not a good attempt; we confirmed every optimum in the table independently with dynamic programming. Harder shapes from the same bench, a 150-decision set cover and an 80-decision selection under three resource limits, prove in under 150 ms. The rows past 100 were engine measurements before they were product limits, which is the order these ceilings are supposed to move in: on 2026-09-04 the in-browser ceilings rose to 2,000 continuous cells (600 constraints) and 500 integer cells, after a pricing fix released Bland's rule from a stuck state and the pivot loop stopped maintaining sealed columns. The measurements behind the raise: dense 2,000×600 solves in 1.3 s, 500 dense binaries prove in 1.6 s, and single-resource selections prove 1,500 in 350 ms, all in the same seeded harness as this page. The sheet-path ceilings are unchanged: that path is bounded by round trips to Google, not by the engine, and it moves only on live-sheet measurement.

Yes/no decisionsResultTime
25proven optimal0.6 ms
40proven optimal0.8 ms
60proven optimal3.5 ms
100proven optimal8.2 ms
150proven optimal0.5 ms
200proven optimal13.0 ms
300proven optimal35.7 ms

Measured: Monte Carlo

Simulation runs in the browser, so the sheet is compiled once and never recalculated per trial. Three uncertain inputs, seeded:

TrialsTime
1,0001.0 ms
10,000 (the default)5.9 ms
100,00057 ms

Measured: nonlinear and evolutionary search

These two are search engines, not proof engines. They can land on an excellent answer but cannot certify one, so their statuses name the algorithm and the reason it stopped, and never say proven. For a search engine the first number is answer quality, not speed: the share of the distance from the starting point to the true optimum that the search covers at its default budget, on seeded instances whose optimum we computed independently of the engine. 100% means the answer equals the known optimum to full precision. It still is not a proof.

NonlinearGap closedTime
5-variable curved valley100%0.3 ms
30-variable ill-conditioned quadratic100%1.5 ms
60-variable ill-conditioned quadratic100%3.1 ms
10 variables on an equality constraint99.99999%10.4 ms

The nonlinear engine gained a derivative-based search this sprint (L-BFGS on numeric gradients), with the previous pattern search kept as the fallback and the finishing polish. On the 30-variable quadratic above, the old search stopped at 99.25% of the gap after 332 ms; the new one covers all of it in under 2 ms. A model whose surface defeats gradients falls back automatically, and the status says which search produced the answer and why it stopped.

EvolutionaryGap closedTime
15-variable smooth model99.992%10.8 ms
25 yes/no decisions98.5%9.7 ms
40 yes/no decisions92.3%11.1 ms
60 yes/no decisions92.6%17.4 ms

The yes/no rows are the honest ones: at its default budget the evolutionary search now covers 92 to 98% of the gap on 0/1 selection problems, up from 82 to 88% before this sprint reworked its selection and adaptation rules. It is also why a whole-number model that fits the Simplex LP engine belongs there instead: the LP path proves the same shapes outright, in milliseconds, in the table further up.

Measured: decision trees and the project tools

Most of these were already instant. Measured is still better than assumed, so here they are, at the size a template loads and at the largest instance each tool accepts. The engine families that shipped after this table was measured have their own table below.

ToolTemplate sizeLargest accepted
Decision tree rollback15 nodes, 0.02 ms32,767 nodes, 34.6 ms
Decision tree break-eveninside a 1.0 ms full run1,023 nodes, 280 ms
Critical path (CPM)10 tasks, 0.03 ms2,000 tasks, 4.2 ms
Critical chain (CCPM)10 tasks, 0.02 ms2,000 tasks, 3.0 ms
Resource load10 tasks, 0.03 ms2,000 tasks, 4.3 ms
Earned value12 rows, 0.02 ms100,000 rows, 64 ms
Risk register12 risks, 0.01 ms80,000 risks, 78 ms
Schedule risk simulation15 tasks × 5,000 trials, 50 ms200 tasks × 50,000 trials, 6.5 s

Schedule risk pays for what it is, a simulation: 50,000 trials over a 200-task network is ten million sampled task durations, and six and a half seconds is the honest price of the largest run the panel allows. The default run is 50 ms.

Measured: the newest engines

Four engine families shipped after the tables above were measured, so they get their own table rather than a silent absence. These are engine timings, measured off-sheet like the optimization figures above: seeded instances, two unmeasured warm-up runs, the median of repeated runs. Measured 5 September 2026.

EngineInstanceTime
Schedule risk over a task network30 tasks on converging paths, PERT durations, 5,000 trials (the default)108 ms
Schedule risk over a task networkthe same network, 20,000 trials432 ms
Tree sensitivity and information31-node, 4-level tree: rollback, the recommended policy, value of information and a 41-point probability sweep, in one pass3.0 ms
ETS forecast, auto scan96 monthly readings, season of 12, 8 models compared, horizon 129.9 ms
ARIMA forecastthe same series: differencing chosen by a stationarity test, then a stepwise order search186 ms
Efficient frontier, risk-aware optimization10 bounds swept on a stocking model, each a full optimization: 1,912,400 model evaluations in all2.3 s

The frontier row is the one to read twice: 2.3 seconds is not one answer, it is ten complete optimizations, each honouring its own requirement bound, about 230 ms apiece. Every point re-runs the search with the same seed and common random numbers, so each frontier point is bit-equal to the standalone run at that bound; the suite pins that. On this instance nine of the ten points came back feasible, from a mean profit of 280.0 unconstrained down to 236.4 at the tightest feasible bound, and the tightest bound of all came back infeasible and said so rather than pretending.

Measured: the 38 data-analysis tools

Every tool was measured at two sizes when it shipped: the size its template loads, and its 500,000-cell input cap. The newest, ETS and ARIMA forecasting, runs on the forecasting engine measured in the table above and keeps its own smaller caps, stated there. At template size every tool answers in under a millisecond, except neural net training at 4 ms and fuzzy lookup at 5 ms. At the cap most still finish well under a second; the table below shows the slowest, because those are the rows worth reading before you paste half a million cells.

At the 500,000-cell capTime
Regression, 30,000 rows × 15 predictors23 ms
Two-factor ANOVA37 ms
Descriptive statistics, 500,000 values206 ms
Rank tests (Mann-Whitney, Wilcoxon, Kruskal-Wallis)under 290 ms
Fuzzy lookup, 700 × 714 keys0.6 s
Holt-Winters forecast, 500,000 points1.7 s
Distribution fitting, 500,000 values0.7 s
Association rules, 50,000 baskets × 10 items1.8 ms
Gain curve, 200,000 scored rows69 ms
ROC and AUC, 200,000 scored rows87 ms
Classification tree, 50,000 rows × 10 columns, depth 4260 ms
k-means, 50,000 rows × 10 columns, k=82.0 s
Neural net at the panel maxima (3,000 epochs, 64 hidden)7.0 s

The ANOVA row moved this sprint: at the cap it cost nearly ten seconds until measurement found a quadratic array build, and it is 37 ms now, with the pinned textbook values unmoved and the output identical to the bit. Distribution fitting and the Holt-Winters forecast each shed more than a third the same way. The classification tree row moved the same way this time: it was re-partitioning the node from scratch for every candidate threshold, one sort per feature now does the same scoring, and the row went from 2.4 seconds to 260 ms with the fitted trees identical to the bit. The largest rows moved again this round, for a smaller reason: in the sandbox these tools run in, every call to a global like Math.tanh pays a lookup through a proxy, and the hottest loops were paying it hundreds of millions of times. Resolving each global once per call, and nothing else, took the neural net ceiling from 25.5 seconds to 7, k-means from 4 seconds to 2, distribution fitting from 2.1 seconds to 0.7 and fuzzy lookup from 1.4 seconds to 0.6; schedule risk shed its per-trial bookkeeping the same way and halved. Every output is identical to the bit. The slow rows that remain are honest compute at deliberate maxima: k-means is five restarts of up to a hundred passes over the data, and the neural net figure is the panel's absolute ceiling, three thousand epochs; its default configuration trains in 4 ms.

Association rules, where the cost is not the row count

The 1.8 ms above is the least interesting number on this page, because transactions are not what makes market basket analysis expensive. The item count is. Apriori builds every combination of items that clears your support floor, so the work grows with the width of the table and the looseness of the threshold, not with how many baskets you paste in. Fifty thousand baskets across ten items is nothing; two thousand baskets across a hundred items is a hundred times the work.

2,000 baskets, dense, 5% supportCombinations builtTime
40 items, up to 3 per rule10,70017 ms
60 items, up to 3 per rule36,05058 ms
100 items, up to 3 per rule166,750405 ms
80 items, up to 4 per rule, 2% support1,666,980refused in 0.2 s

That last row is the honest one. Before this was measured it ran for twenty-one seconds and built 1.67 million combinations, on its way to six and a half million rules, to show the two hundred with the highest lift. That is not a slow answer, it is a memory hazard on a server with a hard time limit, and a partial answer would misrepresent what the data says. So the search now stops before the allocation and says which of the two settings to change: raising minimum support cuts the combinations hardest, and lowering the items-per-rule cap is the other lever. Every shape that finishes in under half a second still runs.

What the classifiers are measured on, and against

Logistic Regression and the Classification Tree both keep a quarter of your rows back, chosen with a seeded stratified split so both outcomes appear on both sides, and score themselves on rows they never fitted. Both figures are printed together, because the distance between them is the finding: a model that scores far better on its own training rows has memorised rather than learned. The tree also prints what you would get by always guessing the commonest class, and says in words whether it beat that, because ninety per cent correct on data that is ninety per cent one class is the most over-read number in any classification report.

AUC is reported alongside accuracy for the same reason. Accuracy is one point on a curve, chosen at a 0.5 cutoff; AUC is the whole curve, and it is the number that survives a rare outcome. It is computed from the same swept counts the curve is drawn from, so the figure is provably the area of the chart above it, and tied scores collapse to a single threshold, so a model that expresses no ranking at all scores 0.5 rather than something flattering.

What happens at the edge

A limits page is only worth reading if it says what happens when you hit one.

Measured: clicks from model to results

Speed is not only arithmetic. The other cost of a run is the path through the interface, so we counted ours.

The task: a model already sits on your sheet, one input is uncertain, and you want the distribution of one output. Starting from the Sortia home panel, that takes 11 mouse clicks, and 9 with the default bell curve: the Risk Analysis tile, the input cell on the grid, the row's use-selection button, the distribution menu and a shape (skip these two for the default), the three parameter fields, the output cell on the grid, its use-selection button, and Run. Typing the three parameter values is not counted, because every tool makes you type your numbers. No dialog opens, nothing asks OK, and the results chart renders in the panel without a further click.

The counting convention is the strict one, against ourselves: every mouse press on the fair path counts, including clicking into a field before typing in it. One click of that comes back if the uncertain input cell is already selected when you open Risk Analysis: the first row arrives filled from your selection, marked as such, and one click clears it. That makes it ten, and eight with the default bell curve, for a path that starts with the cell already chosen rather than from the home panel with nothing selected. Counted on the shipped panel on 2026-09-02, the template path re-counted on 2026-09-03 after it shortened, and the selection case counted on 2026-09-03 when it shipped; if the panel changes, these numbers are wrong until re-counted, which is why the convention is stated. Starting from one of the 379 templates instead of your own model, the path is three clicks under the same convention: the template gallery, the template's own Use this template button, and Run, with every input distribution arriving prefilled. It was four until the card gained that button, which is the kind of change that makes a published count wrong.

How this was measured

The first benchmark we wrote flattered the engine: its test problems happened to be ones the search could solve without ever branching, and it reported a hard case as instant. We threw it away and replaced it with seeded instances chosen to branch, and the replacement immediately caught a real bug: a search that gave up early while labelling its answer optimal. Both the fix and the benchmark shipped. The figures above are engine timings, measured off-sheet, because the in-browser path has no sheet in the loop; the fallback path is bounded by Google Sheets recalculation rather than by arithmetic, which is why its limits are stated separately.

This sprint extended the same treatment to every remaining engine family, and the families shipped since get the same discipline: the schedule risk engine is pinned bit-for-bit against an independent reimplementation, every frontier point is checked bit-equal to the standalone optimization at the same bound, and the ETS and ARIMA forecasters carry their own pinned test coverage. The search engines are graded on seeded instances whose optima were computed independently, by dynamic programming or in closed form, so a search that flatters itself gets caught by arithmetic. The statistics tools are pinned bit-exact to textbook and reference values, and every speedup above was verified to leave those bits unmoved before it shipped: a faster answer that differs in the last decimal place is treated as a wrong answer.

The same discipline that guards correctness guards this page: automated tests read these limits from the engine source, and the build fails if the two ever disagree. How the answers themselves are checked is on the validation page.

How a measurement changed the engine

The standard advice is that steepest-edge pricing is the best-performing simplex variant. We implemented it in both halves of the solver and let this benchmark decide: it lost in the primal loop and won in the dual repair, so half of it shipped. The numbers, and what we declined.

Estimates in, odds out.

Home· Start here· Templates· Pricing· Teams· Tell someone· Privacy· Terms· Developers· Support· Changelog· Validation· © 2026 Sortia
Google Sheets™ is a trademark of Google LLC. Sortia is not affiliated with or endorsed by Google.