Skip to content

expr: Evaluate binary function arguments through a named iterator - #39903

Merged
antiguru merged 2 commits into
MaterializeInc:mainfrom
antiguru:moritz/cpu-315-lazy-binary-eval
Oct 19, 2026
Merged

antiguru merged 2 commits into
MaterializeInc:mainfrom
antiguru:moritz/cpu-315-lazy-binary-eval

Conversation

@antiguru

@antiguru antiguru commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

#39061 made the blanket LazyBinaryFunc impl evaluate both arguments into an array of results. That made case_literal/if_chain/* 11-13% slower, which nightly cargo-bench flags. A named iterator that evaluates each argument on demand restores most of the previous speed and keeps #39061's code-size reduction (cargo llvm-lines -p mz-expr --lib: 1,933,557 lines vs. 1,934,453 on main).

Local A/B against ae3cc46, #39061's parent (median of 3, pinned core): if_chain/64_arms/last 4741 ns before #39061, 5319 ns on main, 4816 ns with this PR. Most of the family lands within 2.3% of the parent. 4_arms/last is the exception (306 / 340 / 333 ns), and it reproduced in two runs.

Every binary function's input is a 2-tuple whose try_from_iter pulls both arguments before it checks for errors and NULLs, so evaluation semantics are unchanged. A new case in error_semantics.slt pins that an eval error wins over a NULL in either argument position.

Closes: CPU-315

🤖 Generated with Claude Code

The blanket `LazyBinaryFunc` impl evaluated both arguments into an array
of results before handing them to `InputDatumType::try_from_iter`. This
made every binary function evaluation about 12% slower on the
`case_literal/if_chain` benchmarks. A named iterator that evaluates each
argument on demand restores the previous speed and keeps a single
iterator type per `Eval` implementor, so the generated code size stays
the same.

Closes: CPU-315

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antiguru
antiguru marked this pull request as ready for review October 16, 2026 20:19
@antiguru
antiguru requested a review from a team as a code owner October 16, 2026 20:19
@antiguru
antiguru requested a review from bosconi October 16, 2026 20:20

@bosconi bosconi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaces #39061's [Result<Datum, EvalError>; 2] with a named iterator that evaluates each argument when try_from_iter asks for it, keeping one instantiation per input type and Eval implementor. Semantics are unchanged: every binary function's Input is a 2-tuple generated by #[sqlfunc], and the tuple try_from_iter pulls every argument before it checks for errors and NULLs. So both arguments are still evaluated, and an eval error still wins over a NULL. Looks good.

Reviewed at a765877.

Suggestion

  • With the array, both arguments were evaluated whatever try_from_iter did. Now that rests on the tuple impl consuming everything before it decides, which is easy to "optimize" into a short-circuit later, and I found no test that pins the resulting priority for binary functions. A column-based case in test/sqllogictest/error_semantics.slt would: a table (a int, b int) holding (NULL, 0) and SELECT a + 1/b, expecting the division-by-zero error. Columns keep the optimizer from folding the NULL away.

Nits

  • nit: the description says this restores the previous speed but gives no numbers. The A/B figures from CPU-315 (if_chain/4_arms/last 300-313 ns before #39061, 344-351 ns after) plus this PR's number would make that checkable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antiguru

Copy link
Copy Markdown
Member Author

I adjusted the PR based on the feedback. PR body now includes measurements, and there's an SLT to assert the error semantics. Thanks for the review!

@antiguru
antiguru enabled auto-merge (squash) October 19, 2026 08:19
@antiguru
antiguru merged commit 7020b58 into MaterializeInc:main Oct 19, 2026
78 checks passed
@antiguru
antiguru deleted the moritz/cpu-315-lazy-binary-eval branch October 19, 2026 16:44
@bosconi bosconi added the release-blocker Critical issue that should block *any* release if not fixed label Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-blocker Critical issue that should block *any* release if not fixed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants