Repository navigation
Conversation
Fixes storytold#2813 Cherry-picked onto current main from storytold#2814. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mns (storytold#2813) The column blocks from storytold#2814 keep the constants of one column, so a lookup table or a criteria range made of formulas (`VLOOKUP` over `A$1:B$500`, `SUMIFS` over running ranges of formula columns) is still gathered from the cell store and the results on every read. Outside iterative calculation a formula's result doesn't change once calculated in a pass, so a block now keeps formula results too, and covers any number of columns (keyed by first and last column, row-major). Formulas read for a block are calculated through `cell_value` in the order `range_values` reads them. To keep that order, a block only reads rows of the request: a request that doesn't touch the kept block replaces it instead of filling the rows in between. A read is not kept when a formula in it is still pending (in progress, or abandoned for a suspension) or spills, or when a formula read for it changes the block kept of the same columns in the meantime (grows, replaces or gives it up). While iterating, formulas end the block as before. A read within the kept block is copied straight from it, and growing a block downwards moves the kept values instead of copying them. Formulas read for a block may keep blocks of other columns, so the budget is checked again before the block is kept. Also from CodeRabbit's review of storytold#2814: a block given up for the rest of the pass gives its values back to the budget. And a type alias in storytold#2814's test, for clippy's `type_complexity`. Tests: on random sheets (suspensions, errors, spills, a loop, iteration, RAND) every result and circular reference is the same bit for bit with column blocks on and off; pass-level tests cover a read of a formula in progress, a spill found during the read, a nested read that grows the block, a read away from the kept rows, and the budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Text criteria matching crates/functions/src/criteria.rs |
Text criteria store parsed wildcard tokens. Matching borrows scalar values and uses a byte-based path for ASCII text. |
Column-block cache and range reads crates/calc/src/recalc/pass_host.rs |
PassHost caches eligible column blocks within a per-pass budget. range_values uses the cache for eligible multi-row ranges and otherwise retains the existing range-reading path. |
Calculation and cache validation crates/calc/src/recalc.rs, crates/calc/src/tests.rs |
Tests check cache growth and budget behavior, spills, edits, and formula results against uncached calculations. An ignored timing test compares running COUNTIFS and COUNT recalculation. |
Priority:
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Refactor · Severity of issue fixed: Medium
Sequence Diagram(s)
sequenceDiagram
participant COUNTIFS
participant range_values
participant PassHost
participant read_rows
COUNTIFS->>range_values: request multi-row range values
range_values->>PassHost: request column block
PassHost->>read_rows: read missing rows
read_rows-->>PassHost: return row values
PassHost-->>range_values: return cached block values
range_values-->>COUNTIFS: return range values
Suggested reviewers: aiongg, dylanmtaylor
Merge Risk: 
Minimal · up to dc180
-
No demonstrated calculation or matching regression remains; the PR is mergeable after normal checks.
Security architecture risk: 
Low · up to dc180
-
The optimization keeps cached values within one calculation pass and preserves fallback paths for unfinished formulas, spills and iterative calculation. No introduced security defect was established. Remaining uncertainty concerns peak resource use and volatile-formula behavior in exceptionally deep dependency chains.
Retained concerns
No architecture-level concerns identified.Security review details
-
Security Blast Radius
- inferred — Workbook-controlled formulas, ranges and criteria influence computation and retained values in the calculating process. Cache identity includes the sheet and exact column span, and its lifetime is confined to the host. The inspected path establishes no cross-workbook cache sharing; shared-service or tenant exposure remains unknown.
Trust Boundaries and Controls
- observed — Wildcard matching retains the preexisting per-operation step budget. Both ASCII and non-ASCII whole-text paths use the budgeted matcher, and wildcard search shares one budget across attempts.
Resilience and Maintainability Implications
- observed — Focused tests cover final-value retention, nested growth, disjoint reads, same-pass spills, nested budget consumption and budget release. A separate cache-on/cache-off test compares values and circular references across recalculation and edits, including spills and iteration. These are inspected assertions, not verified execution results.
Hardening Proposals
- proposed — For deployments that calculate untrusted workbooks in shared processes, consider byte-aware cache accounting and peak-memory checks covering large text values and nested reads. This would strengthen resource containment beyond the retained-slot cap; it is not an observed vulnerability.
-
Pre-merge checks: 
3 · 
1 · 
1
-
Failed checks: 1 warning · 1 inconclusive Passed checks: 3 passed-
Full details: Out of Scope Changes check
-
Explanation
The changes stay within [
#2813]. The cache implementation, criteria matching optimization, and tests support faster growing-rangeCOUNTIFSand related criteria reads. The randomized tests validate cache behavior and result preservation. No unrelated production feature or unrelated test change is demonstrated.
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 13 functions across 4 files.
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
Full details: Linked Issues check
-
Explanation
The implementation directly targets [
#2813].PassHost::column_blockcaches growing multi-column ranges, including formula results, andcriteria.rsavoids repeated wildcard parsing and value cloning. Tests cover the ledger workload, edits above the table, and formula results. The available evidence does not establish the requested performance against the issue's 1,000–8,000-row workload or show its scaling toward Excel. The timing test is ignored and compares COUNTIFS with COUNT, not with Excel.Resolution
Provide reproducible timings for the [
#2813] generator at the issue's row counts, including recalculation after edits, and report the resulting scaling and correctness.
-
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
FredApps
left a comment
There was a problem hiding this comment.
ReviewerA — source-read COMMENT (not an approval)
I read the first hunk: the recalculation tests gain a helper pass_results(cells, order) that calculates a pass with the given formulas in a given order and returns the results, and per the title COUNTIFS and SUMIFS over growing column ranges are sped up. This is the same optimisation as gridcraft#2814 (a COLUMN_CACHE_BUDGET cache for COUNTIFS and SUMIFS passes) and is related to gridcraft#2953 (SUM, COUNT and AVERAGE of constant-only blocks), from the same author as this one: three PRs on one problem that will conflict. The maintainers should choose one design, or say how they combine. The test helper that runs the same cells in different orders is a good basis for the main risk: results must not depend on the order in which cells are calculated. Add tests comparing the cached and uncached results bit for bit for several orders, text and error cells in the range, an edit between passes, and a 50,000-row timing. I did not read the optimisation. Nothing was run.
I ran nothing and have no exact-head CI result for this PR, so this is not an approval. Where a diff was long I read its code hunks rather than every generated or documentation line.
Status: waiting_author.
Fixes #2813
This builds on #2814 by @Silvertree2010 (for #2813). It extends #2814's per-pass column cache from one column of constants to blocks of any number of columns, including formula results.
Why
#2814 keeps the constants of a single column, so that running
COUNTIFS/SUMIFSranges ($B$2:B2,$B$2:B3, …) stop walking the cell store again for every formula. Ranges over formula columns, and blocks of more than one column such as aVLOOKUPtable overA$1:B$500, still take the normal path. On every read, they gather each cell from the cell store and the pass's results.What
main, with Silvertree2010 kept as its author. If Speed up running COUNTIFS/SUMIFS over growing column ranges #2814 merges first, I'll rebase and drop it.cell_value, in the order thatrange_valuesreads them. Outside iterative calculation, a formula's result doesn't change once it has been calculated in a pass, so the kept values stay valid.maindoesn't have.While iterating, a formula ends the block, as in Speed up running COUNTIFS/SUMIFS over growing column ranges #2814.
column_blocks_stay_within_the_budgetcovers it.type_complexityflags one type in Speed up running COUNTIFS/SUMIFS over growing column ranges #2814's test, which now goes through a type alias.Results stay the same
kept_column_blocks_change_no_resultbuilds random sheets, recalculates them, then applies ten rounds of edits. It compares every value bit for bit, and the circular references found, with kept blocks on and off, using a test-only switch. Formulas read growing, moving and fixed blocks of one to three columns, throughSUMIFS,COUNTIFS,AVERAGEIFS,SUMIF,COUNTIF,VLOOKUP,INDEX,MATCHandSUMPRODUCT. The sheets have:RAND, whose values follow the order formulas are calculated in.STACK_BUDGET, evaluation can suspend at a different formula. That changes the order formulas are calculated in, and with it whatRANDdraws. Results that don't depend on that order stay the same.column_blocks_keep_only_final_values);column_blocks_read_no_rows_between);mainand with this PR.Benchmarks
Median time of
formulas.calculateNowthroughgridcraft-cli: release build, manual calculation mode, 30 runs each, Apple M3 Pro. All builds were measured alternately in the same session.mainwindowssmall_formulasrunning_totalsstays at about 21 s with this PR (3 runs per build, which vary by about ±10%, too much to show a difference).On the private workbook, one input case (set the inputs,
calculateNow, read the results) takes 0.94 s onmainand 0.91 s with this PR (0.92 s with #2814 alone). Each is the median of 3 runs of 20 cases, with the builds taking turns.windowshas 4,000 rows:A =ROW()andB =A*1.5;C: aSUMIFSoverB$1:Bnwith two criteria onA$1:An;D: aVLOOKUPintoA$1:B$500;E: aCOUNTIFSoverA$1:An.Synthetic workbooks (openpyxl)
Not covered
Related
SUM/COUNT/AVERAGEover formula cells, building on perf(calc): SUM/COUNT/AVERAGE over big ranges reuse constant-only results #2691) and perf(calc): fewer lookups and allocations in recalculation #2952 (fewer lookups and allocations per cell read). All three changecrates/calc/src/recalc/pass_host.rsnear each other. Whichever lands second may need a small rebase.🤖 Generated with Claude Code
Summary by CodeRabbit