Skip to content

bench: pwmj left semi/anti join - #24160

Merged
kumarUjjawal merged 4 commits into
apache:mainfrom
SubhamSinghal:bench-pwmj
Aug 15, 2026
Merged

bench: pwmj left semi/anti join#24160
kumarUjjawal merged 4 commits into
apache:mainfrom
SubhamSinghal:bench-pwmj

Conversation

@SubhamSinghal

@SubhamSinghal SubhamSinghal commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Which issue does this close?

Benchmark companion to #23870 (LeftSemi / LeftAnti support for
PiecewiseMergeJoinExec), part of EPIC #17427. Closes no issue on its own.

Rationale for this change

#23870 routes existence subqueries with an inequality correlation (WHERE EXISTS (SELECT 1 FROM rhs WHERE lhs.key < rhs.key)) to PiecewiseMergeJoinExec instead of NestedLoopJoinExec. That claim needs a benchmark that can be run on either side of the change, so this PR adds the benchmark separately from the operator work — it is bench code only, no functional change.

What changes are included in this PR?

datafusion/core/benches/pwmj_semi_anti_sql.rs (+ its Cargo.toml entry), a Criterion benchmark with two arms over 20k × 20k Int32 rows, target_partitions=1:

  • pwmj_enabled — flag on
  • nlj — flag off, which can only plan NestedLoopJoinExec

Axes: join type (EXISTS → Semi, NOT EXISTS → Anti) × match regime (all_match 100%, no_match 0%, half_match ~50%, so the buffered side is only partially marked and scan depth varies per streamed row).

Are these changes tested?

Are there any user-facing changes?

No. Benchmark code only.

@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) physical-plan Changes to the physical-plan crate labels Aug 7, 2026
@github-actions github-actions Bot removed the sqllogictest SQL Logic Tests (.slt) label Aug 7, 2026
@codecov-commenter

codecov-commenter commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.05%. Comparing base (e64e3f7) to head (cdd33d9).
⚠️ Report is 77 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #24160      +/-   ##
==========================================
+ Coverage   81.02%   81.05%   +0.02%     
==========================================
  Files        1106     1106              
  Lines      380718   382025    +1307     
  Branches   380718   382025    +1307     
==========================================
+ Hits       308489   309651    +1162     
- Misses      54001    54091      +90     
- Partials    18228    18283      +55     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SubhamSinghal

Copy link
Copy Markdown
Contributor Author

@kumarUjjawal build successful. PTAL

BenchmarkId::new(format!("pwmj_{jt}_{regime}"), right_rows),
|b| {
b.iter(|| {
let (left, right) = build_inputs();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

build_inputs() is timed, including generating 40,000 values and allocating arrays and batches. This can dominate the ~0.5 ms PWMJ result. Could we prebuild the batches and use iter_batched for fresh plans outside the timed section?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

resolved in d52523e

// Selectivity is set by how far the right key range sits above the left range.
// - "high": right keys mostly above left keys -> most left rows match (Semi large)
// - "low": right keys mostly below left keys -> few left rows match (Anti large)
let regimes: [(&str, i32); 2] = [("sel_high", key_span), ("sel_low", -key_span)];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These offsets produce exactly all-match and no-match cases, not “mostly” and “few.” Could we label them as all/none and add an overlapping-range case to measure partial suffix marking?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

resolved in d52523e

Arc::new(Column::new("key", 0)),
);
Arc::new(
PiecewiseMergeJoinExec::try_new(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we benchmark this through the SQL/physical planner instead of constructing PiecewiseMergeJoinExec directly? The current benchmark will panics on because existence joins are unsupported until #23870.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in d52523e

@github-actions github-actions Bot added core Core DataFusion crate and removed physical-plan Changes to the physical-plan crate labels Aug 7, 2026

@kumarUjjawal kumarUjjawal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi @SubhamSinghal Thanks for iterating.

This became a little tricky that's why we had to do more follow up. If you have any ideas you can share as-well.

] {
let ctx = create_context(right_offset, pwmj, &s);
let name = format!("{arm}_{label}_{regime}");
assert_plan_contains(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This still panics on this PR’s current head because enabling PWMJ selects NestedLoopJoinExec until #23870, while this assertion requires PiecewiseMergeJoin. To use this revision as the pre-change benchmark baseline, could we name this arm pwmj_enabled and accept either NLJ or PWMJ, while keeping the disabled arm pinned to NLJ? #23870’s tests can assert that the enabled plan switches to PWMJ.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in cdd33d9

@SubhamSinghal

Copy link
Copy Markdown
Contributor Author

@comphead this is benchmark PR

@kumarUjjawal kumarUjjawal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good 👍

@comphead comphead left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @SubhamSinghal for the PR.
Please update the PR description accordingly

@SubhamSinghal

Copy link
Copy Markdown
Contributor Author

@comphead updated PR description

@SubhamSinghal

Copy link
Copy Markdown
Contributor Author

@kumarUjjawal @comphead can we merge this PR?

@kumarUjjawal

Copy link
Copy Markdown
Contributor

Thank you @SubhamSinghal and @comphead

@kumarUjjawal
kumarUjjawal added this pull request to the merge queue Aug 15, 2026
Merged via the queue into apache:main with commit 4403c5d Aug 15, 2026
39 checks passed
@SubhamSinghal
SubhamSinghal deleted the bench-pwmj branch August 15, 2026 14:26
imtherealnaska pushed a commit to imtherealnaska/datafusion that referenced this pull request Aug 16, 2026
## Which issue does this close?

Benchmark companion to apache#23870 (LeftSemi / LeftAnti support for
`PiecewiseMergeJoinExec`), part of EPIC apache#17427. Closes no issue on its
own.

  ## Rationale for this change

apache#23870 routes existence subqueries with an inequality correlation
(`WHERE EXISTS (SELECT 1 FROM rhs WHERE lhs.key < rhs.key)`) to
`PiecewiseMergeJoinExec` instead of `NestedLoopJoinExec`. That claim
needs a benchmark that can be run on either side of the change, so this
PR adds the benchmark separately from the operator work — it is bench
code only, no functional change.

  ## What changes are included in this PR?

`datafusion/core/benches/pwmj_semi_anti_sql.rs` (+ its `Cargo.toml`
entry), a Criterion benchmark with two arms over 20k × 20k `Int32` rows,
`target_partitions=1`:

  - **`pwmj_enabled`** — flag on
  - **`nlj`** — flag off, which can only plan `NestedLoopJoinExec`

Axes: join type (`EXISTS` → Semi, `NOT EXISTS` → Anti) × match regime
(`all_match` 100%, `no_match` 0%, `half_match` ~50%, so the buffered
side is only partially marked and scan depth varies per streamed row).

## Are these changes tested?

## Are there any user-facing changes?

No. Benchmark code only.
pull Bot pushed a commit to TCeason/arrow-datafusion that referenced this pull request Aug 19, 2026
## Which issue does this PR close?

- Part of apache#17427. Companion to apache#24160, which added this benchmark for
the `LeftSemi`/`LeftAnti` half.

  ## Rationale for this change

`RightSemi`/`RightAnti` range joins plan as `NestedLoopJoinExec` today,
and the existence-join
benchmark added in apache#24160 does not cover them, so there is no way to say
what routing them `PiecewiseMergeJoinExec` would be worth — before or
after such a change lands.

This benchmark runs on either side of it: the `pwmj_enabled` arm accepts
whichever operator the build plans and prints it, so a run on `main`
today is a valid `NestedLoopJoinExec` baseline that a later build
compares straight against.
  ## What changes are included in this PR?

Bench code only, all in `datafusion/core/benches/pwmj_semi_anti_sql.rs`:

- `RIGHT SEMI` / `RIGHT ANTI` cases across the same three match regimes
as the existing ones, written out as joins rather than reached through
`EXISTS` (a decorrelated `EXISTS` gives `LeftSemi`, and which side a
nested-loop join marks is its own choice, so a subquery cannot pin these
join types).
- One `bench_case` runner shared by every case, replacing the previous
inline per-case plumbing, driven by a `Kind` enum with one variant per
`JoinType`. Adding the mark joins later is two more variants rather than
a new code path.

## Are these changes tested?

The guards above are the tests


## Are there any user-facing changes?

No. Benchmark code only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants