Repository navigation
Conversation
…ss (storytold#2681) A block that holds only constants can't change during a pass, so its sum and count are worked out once and reused by every formula that reads the same block. A block that is the same rows from the top but longer (a running total) continues the shorter block's sum, which is the same additions in the same order as one walk from the start. Applied onto current main from storytold#2691 (its branch merged with main, as one commit). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The block sums from storytold#2691 cover blocks of constants only, so a running total over a column of formulas (`=SUM(B$1:B2)`, `=SUM(B$1:B3)`, ...) still walked the whole block once per row, quadratic in the rows. Outside iterative calculation a formula's result doesn't change once calculated in a pass, so `range_sum` now adds formula cells too, read through `cell_value` in the row-major order `visit_range` reads them in. (The issue left blocks with formulas out of the memo.) A walk is not remembered when a formula read for it suspends the evaluation (`MAX_NESTED`) or spills; a block with an error value is remembered as `None` as before, and iteration and spills found earlier in the pass still go the long way. `COUNT` reuses a block's count whatever the sum so far, since it doesn't use the sum (from CodeRabbit's review of storytold#2691). Tests: running totals over formulas walk each cell once and match the top-down sum bit for bit; a spill found during the pass is counted; and on random sheets (suspensions, errors, a loop, iteration, RAND) every result and circular reference is the same bit for bit with block sums on and off. Also `BlockSums` and a type alias in storytold#2691's test, for clippy's `type_complexity`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Range sum contract and aggregation crates/calc/src/eval.rs, crates/calc/src/eval/call.rs |
Host adds an optional method for returning a range’s numeric sum and count. aggregate_area uses available results for eligible COUNT, SUM, and AVG ranges; otherwise, it continues through the existing traversal. |
Per-pass cache and validation crates/calc/src/recalc/pass_host.rs, crates/calc/src/recalc.rs, crates/calc/src/tests.rs |
PassHost caches eligible block sums and extends shorter cached blocks. Tests check running totals, spill cases, cell counts, and results with caching enabled and disabled. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Refactor
Sequence Diagram(s)
sequenceDiagram
participant aggregate_area
participant PassHost
participant sum_block
participant cell_value
aggregate_area->>PassHost: request range_sum
PassHost->>sum_block: extend cached block
sum_block->>cell_value: evaluate formula cells
cell_value-->>sum_block: formula value or suspension
sum_block-->>PassHost: block sum and count or no reusable result
PassHost-->>aggregate_area: cached sum and count or None
Possibly related issues
- Perf: SUM, COUNT and AVERAGE over big ranges cost about 47 ns per cell, and every formula walks the same range again #2681 — The changes add per-pass reuse for range sums and counts, and test running totals over formula ranges.
- Recommendations: fuzz targets, CI checks, resource-limit suite #2166 — The changes add a nesting-limit assertion and tests for formula evaluation under recalculation.
Suggested reviewers: dylanmtaylor, aiongg
Merge Risk: 
Minimal · up to f0643
-
No concrete merge-blocking defect is established by the supplied evidence. Merge after normal validation.
Security architecture risk: 
Minimal · up to f0643
-
The optimization remains within workbook calculation and preserves traversal fallbacks when cached results cannot safely be used. No introduced security weakness was identified in the reviewed change.
Retained concerns
No architecture-level concerns identified.Security review details
-
Security Blast Radius
- inferred — The new mutable state is scoped to one workbook's calculation host, with sheet-qualified keys and no cross-pass retention. The directly evidenced exposure is workbook calculation results and calculation-process resources; tenant or service-level sharing was not established.
Trust Boundaries and Controls
- inferred — The added public Rust method is an evaluator capability rather than a demonstrated external security entrypoint. Formula-controlled ranges continue to use calculator-owned workbook access and existing formula evaluation; the inspected fast path introduces no credential, network or privileged-operation sink.
Resilience and Maintainability Implications
- observed — Iteration and newly discovered spills on the requested sheet bypass cache lookup. A walk that suspends or produces a spill is not committed as a cached numeric result. Existing suspension recovery calculates dependencies before restarting abandoned evaluations, while errors and stored spills force traversal fallback.
-
Pre-merge checks: 
5
-
Full details: Title check
-
Explanation
The title names the main change: per-pass memoization of SUM, COUNT, and AVERAGE over blocks in the calc engine. The raw summary and objectives confirm this is the core change, which now covers ranges with formula cells as well as constant-only ranges. The title uses the conventional perf(calc) form and states the performance intent. It is 79 characters, which is slightly above a short-title ideal but still concise and specific.
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 87.50% which is sufficient. 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 8 functions across 5 files.
-
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.
|
@coderabbitai full review |
✅ Action performed
|
FredApps
left a comment
There was a problem hiding this comment.
ReviewerA — source-read COMMENT (not an approval)
I read the first hunk: the calculation host trait gains range_sum(sheet, range), returning (sum, count) of the numbers in a block that has no error or spilled value, "added row-major from 0.0 exactly as visit_range would hand them over (formula cells calculated in the same order)", or None if the block is not like that or the host can't tell; per the title (#2681) SUM, COUNT and AVERAGE of constant-only blocks are remembered per pass, so a running total over a growing range is not quadratic. The doc comment sets the right requirement: floating-point addition is not associative, so the cached sum must be added in the same order as the plain loop, or results differ in the last bits and a recalculation after a reload differs from the first. Cache hits must be dropped when a cell in the block changes during the pass (iterative calculation), and only constant cells may be in the block. This and gridcraft#2814 (COUNTIFS and SUMIFS column cache) touch the same area; say how they relate. I did not read the cache. Add a property test comparing the cached and uncached results bit for bit on random blocks (with text, blanks, booleans, huge and tiny numbers) and the timing for 50,000 rows. 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.
This builds on #2691 by @vangel76 (for #2681). It extends #2691's per-pass block sums to blocks with formula cells, so each running total down a column of formulas costs one addition instead of a walk over every row above it.
Why
#2691 remembers the sum and count of blocks that hold only constants. #2681 leaves blocks with formulas out of that memo. A common shape is a running total over a calculated column:
B2 =A2*1.01,C2 =SUM(B$2:B2),C3 =SUM(B$2:B3), and so on. With #2691 alone, every row ofCstill walks all the rows ofBabove it, which is quadratic in the rows.What
mainas one commit, with vangel76 kept as its author. If perf(calc): SUM/COUNT/AVERAGE over big ranges reuse constant-only results #2691 merges first, I'll rebase and drop it.range_sumnow also adds formula cells. It reads them throughcell_value, in the row-major order thatvisit_rangeuses. Outside iterative calculation, a formula's result doesn't change once it has been calculated in a pass. So a block's sum stays valid for the rest of the pass, and a longer block of the same rows continues it: the same additions in the same order, starting from+0.0.MAX_NESTED) or spills. A block with an error value is remembered asNone, as in perf(calc): SUM/COUNT/AVERAGE over big ranges reuse constant-only results #2691. Iterative calculation and spills found earlier in the pass still go the long way.COUNTreuses a block's count whatever the sum so far, because it doesn't use the sum. This was CodeRabbit's suggestion on perf(calc): SUM/COUNT/AVERAGE over big ranges reuse constant-only results #2691.type_complexityflags two types from perf(calc): SUM/COUNT/AVERAGE over big ranges reuse constant-only results #2691. They now go through aBlockSumsalias and a type alias in its test.Results stay the same
remembered_block_sums_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 block sums on and off, using a test-only switch. 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.running_totals_over_formulas_walk_each_cell_once: 2,000 running totals walk 2,000 cells in all, and each total equals the top-down sum bit for bit.block_sums_count_values_spilled_in_the_same_pass, plus an assertion added towide_read_at_the_nesting_limit_suspends_once, check the spill and suspension rules within a single pass. Across passes, the next pass would correct a wrong value from the spill it stores, so a whole-workbook test can't see these.+0.0start.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.mainrunning_totalswindowssmall_formulasThe
running_totalstimes onmainand with #2691 alone come from 3 runs per build, which vary by about ±10%.On the private workbook, one input case (set the inputs,
calculateNow, read the results) takes 0.94 s onmainand 0.83 s with this PR (0.93 s with #2691 alone). Each is the median of 3 runs of 20 cases, with the builds taking turns.running_totalshas 20,000 rows:Aa constant,B =A*1.01,C =SUM(B$1:Bn),D =AVERAGE(B$1:Bn).Synthetic workbooks (openpyxl)
Related
crates/calc/src/recalc/pass_host.rsnear each other. Whichever lands second may need a small rebase.🤖 Generated with Claude Code
Summary by CodeRabbit
Summary
SUM,COUNT, andAVERAGEcalculations can be faster for eligible ranges, while maintaining the same results as standard calculations.