Make ScalarFn array validity lazy when the function defines a validity expression#8336
Conversation
…y expression Previously ValidityVTable<ScalarFn> always eagerly executed the validity expression via the legacy session. Now, when the scalar function provides a validity expression over its inputs, the expression is converted into a lazy ScalarFn array DAG instead: Literal nodes become ConstantArrays, ArrayExpr leaves unwrap to the child arrays they hold, and interior nodes become lazy ScalarFn arrays. Constant results are folded back into AllValid/AllInvalid via child_to_validity. Functions that do not define a validity expression (e.g. Kleene logic and/or, where validity depends on the computed values) keep the eager path. The erased fallback for these is is_not_null over the expression itself, so a lazy representation would be self-referential: resolving the validity of the inner node spawns another is_not_null DAG, which recurses without ever shrinking (this manifested as a stack overflow in element-wise execution paths). ScalarFnRef::validity_opt is added to expose whether a function defines its own validity expression. https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
…to claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity
Merging this PR will degrade performance by 17.27%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Simulation | varbinview_zip_block_mask |
2.9 ms | 3.7 ms | -21.57% |
| ❌ | Simulation | bitwise_not_vortex_buffer_mut[128] |
216.9 ns | 275.3 ns | -21.19% |
| ❌ | Simulation | bitwise_not_vortex_buffer_mut[1024] |
278.6 ns | 336.9 ns | -17.31% |
| ❌ | Simulation | bitwise_not_vortex_buffer_mut[2048] |
342.2 ns | 400.6 ns | -14.56% |
| ❌ | Simulation | varbinview_zip_fragmented_mask |
6.2 ms | 6.9 ms | -11.27% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/cool-bardeen-l8jlsy-4-lazy-scalarfn-validity (d72800b) with claude/cool-bardeen-l8jlsy-3-definitely-all-invalid (365401e)1
Footnotes
|
Investigated the CodSpeed
The same flavor of noise shows in the rest of the stack: the I can't acknowledge regressions on the CodSpeed dashboard from this session — that needs someone with dashboard access. https://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj Generated by Claude Code |
| /// Transforms the expression into one representing the validity of this expression. | ||
| pub fn validity(&self, expr: &Expression) -> VortexResult<Expression> { | ||
| Ok(self.0.validity(expr)?.unwrap_or_else(|| { | ||
| Ok(self.validity_opt(expr)?.unwrap_or_else(|| { |
There was a problem hiding this comment.
do you want to remove the TODO?
Summary
PR 4 of a 4-PR stack (stacked on #8335) — the payoff: lazy validity for
ScalarFnarrays.Previously
ValidityVTable<ScalarFn>always eagerly executed the validity expression via the legacy session. Now, when the scalar function provides a validity expression over its inputs, the expression is converted into a lazy ScalarFn array DAG instead:Literalnodes →ConstantArrayArrayExprleaves → unwrap to the child array they holdScalarFnarrays viaArray::<ScalarFn>::try_newAllValid/AllInvalidviachild_to_validityWhy the eager path remains for some functions
Functions that don't define a validity expression (Kleene
and/or, where validity depends on the computed values) keep the eager path. The erased fallback for these isis_not_null(expr)— self-referential: lazily materializing it means resolving the validity of the inner node, which spawns anotheris_not_nullDAG over a fresh copy of the same node, recursing without ever shrinking. This manifested as a stack overflow in element-wise execution paths (execute_scalar→is_invalid→validity()), caught bytest_bool_consistency. The newScalarFnRef::validity_optexposes whether a function defines its own validity expression so the vtable can pick the right path.Checks
cargo nextest run -p vortex-array(2962 passed)cargo nextest run --workspace --exclude vortex-cuda --exclude vortex-nvcomp --exclude vortex-tensor --exclude vortex-duckdb(passed)cargo nextest run -p vortex-duckdb(196 passed; 2 network-dependent tests excluded)cargo clippy -p vortex-array --all-targets,cargo +nightly fmt --allhttps://claude.ai/code/session_01VPQ7dfZtijfrsjAipwXvEj
Generated by Claude Code