pjfanning opened a new pull request, #1321:
URL: https://github.com/apache/poi/pull/1321

   Item 3 of the evaluator performance review.
   
   ## Before
   
   Whether an operator with an area operand is evaluated element-wise depends 
on whether its result ends up in an `ArrayMode` function (SUMPRODUCT, INDEX, 
XLOOKUP …). `isConsumedByArrayModeFunction` (from #1299) answered that per 
operation by walking the *remaining* tokens while tracking the result's stack 
position — O(n) per operation with an area operand, O(n²) per formula in the 
worst case (`SUM(A:A)+SUM(B:B)+…`) — and identified built-in functions with 
`FunctionEval.getBasicFunction`, which **throws** `NotImplementedException` for 
unimplemented functions; the walk caught it, so any formula containing such a 
function paid a constructed exception per area operation per evaluation.
   
   ## After
   
   `findArrayModeOperands(Ptg[])` does it for the whole formula in one pass: 
replay the RPN structurally (a stack of producer indices), record the consumer 
of every value and whether each function is `ArrayMode`, then propagate 
`consumed[k] = arrayMode[consumer] || consumed[consumer]` in a backward sweep 
(consumers always follow their operands in RPN). `evaluateFormula` computes it 
lazily on the first operation that actually has an area operand — most formulas 
never do — and reads one flag per operation after that.
   
   Semantics are unchanged; the structural rules are the same as the walk's 
(`tAttrSum` is `SUM`, other attribute/control/mem tokens are transparent, 
`UnionPtg` consumes two). The one place that needed thought is external 
("future"/UDF) functions: the walk read the function's name from the 
`FunctionNameEval` already on the runtime stack; the pass resolves the name 
token statically — `NamePtg` via `isFunctionName()`, `NameXPxg`/`NameXPtg` the 
way `getLocalNameXEval` would, and only for this workbook (a name in another 
workbook never yields a `FunctionNameEval`, so the walk said "no" there too). 
No name formula is evaluated by the pass.
   
   `FunctionEval.getBasicFunctionOrNull(int)` (`@since 6.0.0`) is the 
non-throwing lookup; `getBasicFunction` is unchanged.
   
   ## Tests
   
   `TestArrayModeOperands`: element-wise inside SUMPRODUCT/INDEX including 
through an enclosing operator and through a non-array function 
(`SUMPRODUCT(SUM(A1:A3*2),1)` = 12); row-reduction outside (`A1:A3*2` = A2*2, 
`#VALUE!` on a row outside the area), and — the cases the per-token flags 
matter for — an array function elsewhere in the formula not affecting operators 
it does not consume (`A1:A3*2+SUMPRODUCT(B1:B3,C1:C3)`, 
`SUMPRODUCT(A1:A3*2)+A1:A3*3`); XLOOKUP with `(A1:A3=2)*(B1:B3=20)` as lookup 
array (the external-function path); and the raw per-token flag arrays for three 
formulas. All of the behavioural assertions pass on trunk as well, so this is a 
pure refactor for cost.
   
   Locally green: `ss.formula.*` (both modules), `TestHSSFFormulaEvaluator*`, 
`TestFormulaEvaluatorBugs`, `TestBugs`, `ss.usermodel.*`, 
`TestXSSFFormulaEvaluator*`, `TestXSSFXLookupFunction`, 
`TestFormulaEvaluatorOnXSSF`, `TestXSSFBugs` (pre-existing 
`stackoverflow23114397` aside).
   
   `changes.xml` left for you.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to