bench: keep the expect_plan check out of the measured region - #25441
Merged
Merged
Conversation
`expect_plan` was checked on every iteration, inside the timed region,
and it rendered the plan with `{:#?}`. A derived `Debug` prints every
`RecordBatch` an in-memory source holds, so for a benchmark whose tables
come from `CREATE TABLE ... AS SELECT` the dump is megabytes per
iteration. In the `null_aware_join` suite this was most of the measured
time.
The check now renders the plan as `EXPLAIN` displays it, and runs on the
first iteration only, because a plan does not change between iterations.
A failure also prints an 8-line plan instead of a 1.8 MB dump.
`null_aware_join`, release, apache/main, median of 30 iterations:
| Query | before | after |
|-------|--------|-------|
| Q01 | 17.6 ms | 4.6 ms |
| Q02 | 15.3 ms | 2.6 ms |
| Q03 | 14.7 ms | 2.1 ms |
| Q04 | 0.9 ms | 0.4 ms |
| Q05 | 0.9 ms | 0.4 ms |
| Q06 | 0.8 ms | 0.3 ms |
| Q07 | 0.9 ms | 0.3 ms |
| Q08 | 1.2 ms | 0.5 ms |
The display form spells the flag as `null_aware`, not `null_aware: true`,
so the six `null_aware_join` checks change with it. The other 89
`expect_plan` strings are operator names and are not affected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #25441 +/- ##
==========================================
- Coverage 82.33% 82.33% -0.01%
==========================================
Files 1137 1137
Lines 432391 432530 +139
Branches 432391 432530 +139
==========================================
+ Hits 356029 356141 +112
- Misses 54845 54847 +2
- Partials 21517 21542 +25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
|
Thanks @adriangb this is a nice improvement! |
haohuaijin
pushed a commit
to haohuaijin/arrow-datafusion
that referenced
this pull request
Sep 19, 2026
…25441) ## Which issue does this PR close? - N/A. This is a benchmark harness fix found while measuring apache#25339. ## Rationale for this change A benchmark file can assert a string in the physical plan with the `expect_plan` directive. That check ran on every iteration, inside the measured region, and it rendered the plan with `{:#?}`. A derived `Debug` for an in-memory source prints every `RecordBatch` the source holds. Thus, for a benchmark whose tables come from `CREATE TABLE ... AS SELECT`, the check builds a multi-megabyte string per iteration and then searches it. For the `null_aware_join` suite, whose tables are built from `range()`, that string is 1.85 MB and the check is most of the measured time: the suite reports 17.6 ms for Q01, while `datafusion-cli` runs the same query in 4 ms. This inflates absolute numbers and, more importantly, dilutes the A/B difference the benchmark exists to show. ## What changes are included in this PR? The check now renders the plan as `EXPLAIN` displays it, through `DisplayableExecutionPlan`. It also runs on the first iteration only, because a plan does not change between iterations. `null_aware_join`, release build, both sides at the same commit (`c4f5a9e0f2`), median of 30 iterations: | Query | before | after | |---|---|---| | Q01 | 17.6 ms | 4.6 ms | | Q02 | 15.3 ms | 2.6 ms | | Q03 | 14.7 ms | 2.1 ms | | Q04 | 0.9 ms | 0.4 ms | | Q05 | 0.9 ms | 0.4 ms | | Q06 | 0.8 ms | 0.3 ms | | Q07 | 0.9 ms | 0.3 ms | | Q08 | 1.2 ms | 0.5 ms | A failed check now also prints an 8-line plan instead of a 1.8 MB dump. There are 96 `expect_plan` strings in the suites. 89 are operator names, which both forms print. The 6 `null_aware_join` strings that read `null_aware: true` become `null_aware`, which is how `HashJoinExec` displays the flag; this PR changes those 6 lines. The one remaining string, h2o's `output_ordering=[pk@0 ASC NULLS LAST, ob@1 DESC]`, holds in the display form as well. ## What is the testing strategy for this PR? - New unit test `run_checks_expect_plan_once_per_benchmark`: the first run checks the plan, and a later run does not repeat the check. The existing tests still cover the accept and reject paths of a first run. - I ran the suites that need no downloaded data, and all their `expect_plan` strings still hold: `null_aware_join` (8 queries), `smj` (26 queries, which include the 3 `LeftMark` checks), `nlj` (4 queries) and `array_agg_distinct`. - For the h2o `output_ordering` string, which needs data I do not have, I built an equivalent case: a Parquet table with `WITH ORDER`, a window query over it, and that `expect_plan` line. It passes. - Counterfactual check: with a string that is not in the plan, the first iteration still fails, and the message names the string. - `cargo test -p datafusion-benchmarks --lib` passes (143 tests). `cargo fmt` and clippy are clean. ## Are there any user-facing changes? No. This changes the benchmark harness and its documentation only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
Rationale for this change
A benchmark file can assert a string in the physical plan with the
expect_plandirective. That check ran on every iteration, inside the measured region, and it rendered the plan with{:#?}.A derived
Debugfor an in-memory source prints everyRecordBatchthe source holds. Thus, for a benchmark whose tables come fromCREATE TABLE ... AS SELECT, the check builds a multi-megabyte string per iteration and then searches it. For thenull_aware_joinsuite, whose tables are built fromrange(), that string is 1.85 MB and the check is most of the measured time: the suite reports 17.6 ms for Q01, whiledatafusion-cliruns the same query in 4 ms.This inflates absolute numbers and, more importantly, dilutes the A/B difference the benchmark exists to show.
What changes are included in this PR?
The check now renders the plan as
EXPLAINdisplays it, throughDisplayableExecutionPlan. It also runs on the first iteration only, because a plan does not change between iterations.null_aware_join, release build, both sides at the same commit (c4f5a9e0f2), median of 30 iterations:A failed check now also prints an 8-line plan instead of a 1.8 MB dump.
There are 96
expect_planstrings in the suites. 89 are operator names, which both forms print. The 6null_aware_joinstrings that readnull_aware: truebecomenull_aware, which is howHashJoinExecdisplays the flag; this PR changes those 6 lines. The one remaining string, h2o'soutput_ordering=[pk@0 ASC NULLS LAST, ob@1 DESC], holds in the display form as well.What is the testing strategy for this PR?
run_checks_expect_plan_once_per_benchmark: the first run checks the plan, and a later run does not repeat the check. The existing tests still cover the accept and reject paths of a first run.expect_planstrings still hold:null_aware_join(8 queries),smj(26 queries, which include the 3LeftMarkchecks),nlj(4 queries) andarray_agg_distinct.output_orderingstring, which needs data I do not have, I built an equivalent case: a Parquet table withWITH ORDER, a window query over it, and thatexpect_planline. It passes.cargo test -p datafusion-benchmarks --libpasses (143 tests).cargo fmtand clippy are clean.Are there any user-facing changes?
No. This changes the benchmark harness and its documentation only.
🤖 Generated with Claude Code