Skip to content

refactor: keep the hash join NOT IN rules in one module - #26647

Merged
jayzhan211 merged 6 commits into
apache:mainfrom
jayzhan211:hj-not-in/1-move-helpers
Oct 24, 2026
Merged

jayzhan211 merged 6 commits into
apache:mainfrom
jayzhan211:hj-not-in/1-move-helpers

Conversation

@jayzhan211

@jayzhan211 jayzhan211 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

  • None.

Rationale for this change

HashJoinExec supports SQL NOT IN through null_aware = true, in three forms: LeftAnti, LeftMark and RightAnti, each correlated or not. The rules for which rows such a join emits are spread out today:

  • exec.rs: NullAwareMode, four JoinLeftData fields and one JoinBuildData field that only some forms use, and the correlated per-row state built inside the build function.
  • stream.rs: about 700 lines inside the regular join stream. These are the batch-skip decision, the correlated marking pass and its helpers, the RightAnti NULL and UNKNOWN drops, and two final-stage functions.
  • utils.rs: a second copy of the final rule, used for the LeftMark column.

The two copies of the final rule already drifted once: LeftMark checked logical NULLs since #25693, LeftAnti physical ones until #26578.

This code changes often. Since July it gained four features (#23957, #21585, #19857, #26213) and seven correctness fixes (#23104, #23173, #25076, #25339, #25693, #26586, #26578), and #26214, #25679 and #26088 propose more. Each such change has to find the NOT IN rules in three files, usually about six functions, interleaved with the regular join. A change to the regular probe loop has to read past them too.

This PR moves the rules into hash_join/not_in.rs, with one copy of the final rule, so a NOT IN change touches one module and a regular-join change doesn't need to read it. The join reaches it at four points:

  1. NotIn::try_new decides when the plan is built or executed whether null_aware is legal for the join, and which form it is: BuildRows { scope, output: Rows | Mark } for LeftAnti and LeftMark, or ProbeRows { scope } for RightAnti, where scope is Uncorrelated or Correlated.
  2. NotInBuild::try_new runs once per build. It has one variant per form, holding only that form's state. JoinLeftData keeps a single Option<NotInBuild>.
  3. NotInProbe::before_lookup / after_lookup run around each probe batch's lookup, and after_empty_build replaces after_lookup when the build map is empty. They skip the batch, record what the probe saw, mark build rows or this batch's probe rows UNKNOWN, and drop the probe rows a RightAnti must not emit; for other joins they do nothing.
  4. NotInBuild::final_chunk runs on the build rows emitted at the end. It applies one rule for an unmatched row's mark, used both by LeftAnti (keep the rows whose mark is FALSE) and by LeftMark (the mark column).

What changes are included in this PR?

Six commits, each compiling and passing clippy on its own:

  1. Move the correlated marking helpers into not_in.rs. The code is unchanged, apart from one signature that now takes the build data and probe batch instead of the stream's private state structs. This commit also adds a test for correlated marking when every build value key is NULL. The full-key map is empty then, and the marking must still run before the empty-map shortcut; nothing covered this before.
  2. NotIn replaces NullAwareMode. The legality checks, messages and order are the same.
  3. NotInBuild holds the per-form build state, with one final rule. It replaces the JoinLeftData / JoinBuildData fields, the two final-stage functions in stream.rs, and build_null_aware_left_mark_column in utils.rs.
  4. The NotInProbe hooks replace the stream's NOT IN branches and fields. The correlated RightAnti's per-batch UNKNOWN probe rows move from ProcessProbeBatchState into NotInProbe, which recomputes them on each batch's first lookup.
  5. Review follow-ups. HashJoinExec::not_in() and the not_in parameters replace null_aware_mode() and null_aware: Option<NotIn>, the helpers used only inside not_in.rs become private, and final_chunk asserts that a probe side with a NULL key is not empty.
  6. Test the correlated RightAnti empty-build path. test_null_aware_correlated_all_null_build_values also runs RightAnti, which must drop the UNKNOWN probe rows when the build map is empty (NotInProbe::after_empty_build).

Types: NullAwareMode goes, and NotIn, NotInScope, NotInOutput, NotInBuild and NotInProbe come in. Gone: the NOT IN fields of JoinLeftData, JoinBuildData, HashJoinStream and ProcessProbeBatchState and their constructor parameters, 4 stream functions (null_aware_skip_probe_batch, drop_null_probe_keys, null_aware_left_anti_final_indices, null_aware_left_mark_column), and the second copy of the final rule.

What does not change:

  • the planner rules, including the JoinSelection swap to RightAnti;
  • the dynamic-filter gate for null-aware joins;
  • the prefilter and prepared-build exclusions;
  • the cross-partition ProbeCompletion.

Behavior notes

No query results change. Two internal orders do:

  • Reservation order. For an uncorrelated LeftAnti or LeftMark join whose build key has newly materialized logical NULLs (a dictionary, REE or Null key), the NULL buffer is now charged to the memory reservation right after the visited bitmap. On main it was charged after the membership step's optional pruning bitmap. Under a tight memory limit, a different allocation may be the one that fails. The pruning bitmap only exists with a dynamic filter, which a null-aware join with a nullable build key doesn't get.
  • The form is computed twice per execution. The stream reads it from the build (JoinLeftData::not_in); execute derives it again (HashJoinExec::not_in()) for the dynamic filter. Main derived it three times (build, stream, accumulator). The two can only disagree if an executed plan is rebuilt and run again without reset_state, which is already unsupported. Main panicked in that case; this PR uses the build's form.

Performance

Benchmark bot (GKE c4a-highmem-16, aarch64) on a9d4896d7e against its merge base 702803d022; the later commit only adds a test:

  • null_aware_join: geomean 0.989. Q01 and Q03 are 4% faster, Q02, Q05 and Q06 about 2% faster, Q08 2% slower (20.4 → 20.8 ms), the rest are within 1%.
  • hj: total 6870 → 6885 ms (+0.2%). Q22 and Q24 are unchanged. The queries the bot flags (Q11, Q14, Q15, Q17 slower; Q16, Q18 faster) move within their run-to-run spread.
  • tpch10: all 22 queries unchanged, total 7078 → 7049 ms.

Earlier runs on an M-series Mac showed this refactor slowing hj Q24 by up to 9% through different LLVM inlining of the probe loop. That doesn't reproduce on the bot, so the PR leaves inlining to the compiler. Not measured on x86_64.

What is the testing strategy for this PR?

  • New test: test_null_aware_correlated_all_null_build_values, covering LeftAnti, LeftMark and RightAnti with the probe in two batches, at every batch-size config.
  • Existing tests:
    • physical-plan unit tests;
    • the null-aware join fuzz test (null_aware_join_fuzz, with extended_tests), which covers uncorrelated and correlated RightAnti plans after the JoinSelection swap;
    • the null_aware_* and subquery* sqllogictest files, with no changes to expected results or EXPLAIN output.

Are there any user-facing changes?

No.

@github-actions github-actions Bot added the physical-plan Changes to the physical-plan crate label Oct 23, 2026
@jayzhan211 jayzhan211 changed the title refactor: move the NOT IN marking helpers into hash_join/not_in.rs refactor: keep the hash join NOT IN rules in one module Oct 23, 2026
@jayzhan211
jayzhan211 force-pushed the hj-not-in/1-move-helpers branch from caddf57 to 655d848 Compare October 23, 2026 08:20
@github-actions github-actions Bot added optimizer Optimizer rules core Core DataFusion crate labels Oct 23, 2026
@jayzhan211
jayzhan211 marked this pull request as ready for review October 23, 2026 13:27

@viirya viirya 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.

Thanks, this is a nice consolidation. Having one copy of the final rule removes the kind of drift #26568 came from, and the probe loop is much easier to follow with two hook calls than with the NOT IN branches interleaved. I compared the moved rules with main form by form (legality order, the uncorrelated skip/record, correlated marking before the empty-map return, and the LeftAnti/LeftMark final rules) and they line up.

#26578 has merged since this was opened, so it needs a rebase. That should make NotInBuild::try_new a little simpler, see inline. The description's notes on LeftAnti physical NULLs and on the reservation order will need updating too; the latter now applies to LeftAnti as well.

The new test_null_aware_correlated_all_null_build_values is a good addition. It pins exactly the ordering this refactor moves: before_lookup must run before the empty-map return, or LeftAnti would emit all three rows.

My main question is about the two inlining pins, inline. The rest are small.

} => {
// `LeftMark` uses logical NULLs; `LeftAnti` still uses
// physical ones (#26568).
let key_nulls = match output {

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.

With #26578 merged, LeftAnti uses logical nulls too, so this match output can collapse to keys[0].logical_nulls() and the #26568 comment can go. The reservation check below then covers both outputs the same way.

}
// A probe batch with a NULL key also has rows: `has_null` implies
// `non_empty`.
Self::Uncorrelated { output, .. } if summary.has_null => {

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.

Main's LeftMark rule didn't depend on has_null ⇒ non_empty, but this ordering does: a NULL build key under has_null relies on non_empty being true. It holds today because before_lookup is the only caller of record_probe_batch, but ProbeSideSummary is a plain struct with public fields. Would a debug_assert!(!summary.has_null || summary.non_empty) here be worth it, so a future caller that breaks it fails a test instead of silently changing marks?


// Kept out of line: inlined into `HashJoinStream::process_probe_batch`,
// this loop measured ~4-6% slower on high-fanout probes.
#[inline(never)]

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.

The effects measured for this pin and for the #[inline(always)] on process_probe_batch (4–9%) aren't far above the 2.5% that symbols alone moved Q24, and they were measured on one aarch64 machine. Do they hold on x86_64? If not clearly, I'd lean toward leaving both out of this refactor or moving them to a separate perf PR. They encode one LLVM version's inlining decisions, and the percentages in the comments will go stale as the surrounding code changes. This one is also on a pub method, so it applies to every caller, not just the hash join probe.

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.

I only measured these on aarch64, so I've left both pins out. The Performance section of the description now says what they recovered there and that it isn't measured on x86_64. If one turns out to be worth keeping, I'll send it as its own PR.

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.

Thanks for taking them out. The bot's hj run on a9d4896 shows Q22 and Q24 unchanged (both slightly faster at the minimum), and the few flagged queries (Q11/Q14/Q15/Q17 slower, Q16/Q18 faster) look like run-to-run variance given their stddevs; tpch10 is flat. So the earlier regression doesn't reproduce on this aarch64 runner without the pins. Once null_aware_join is in too, this looks good to me without them.

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.

null_aware_join is in as well: 11 of 12 queries are unchanged or faster. Q08 is 2% slower (20.4 → 20.8 ms), but Q06, with the same NULL fraction and no scope key, is 2% faster, and Q08's marking path is moved unchanged, so I read it as code placement. I'm fine leaving the pins out.

/// Updates state to `FetchProbeBatch`
// Kept inlined into `poll_next_impl`: called out of line, the probe
// loop measured ~4-9% slower (`hj` Q24, `null_aware_join` Q06).
#[inline(always)]

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.

Same question as on ArrayMap::get_matched_indices_with_limit_offset: was this measured on x86_64 as well?

/// to `+0.0` for key comparison. The dynamic filters and
/// `build_side_has_null` were derived from the original keys instead.
/// to `+0.0` for key comparison. The dynamic filters and the `NOT IN`
/// state were derived from the original keys instead.

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.

This (and the JoinBuildData::values doc at line 214) now says the NOT IN state was derived from the original keys. The NULL-value masks are, but the correlated NullValueBuildRows::scope_values are filtered from normalized_values. The old wording named only build_side_has_null, which was accurate. Maybe say "the dynamic filters and the NOT IN NULL masks"?

/// After a lookup: [`NotIn::ProbeRows`] drops the probe rows whose key is
/// NULL, since `NULL NOT IN (..)` is never TRUE. `valid_keys` is the probe
/// keys' validity (`matchable_join_keys`), when the batch has one.
pub(super) fn after_lookup(

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.

before_lookup is a &mut self method that dispatches on the build, while this is an associated fn on NotInProbe that takes &NotInBuild and only does anything for ProbeRows. It uses none of NotInProbe's scratch state, so would it read more naturally as a method on NotInBuild, next to final_chunk?

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.

After the rebase onto #26213, after_lookup also drops the probe rows that before_lookup marked UNKNOWN for a correlated RightAnti. That bitmap is per-batch scratch, so it lives in NotInProbe, and after_lookup is now a &self method there that uses it.

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.

Makes sense. With unknown_probe_rows living in NotInProbe, keeping it there reads fine.


/// Returns the rows where at least one of `value_keys` is NULL, or `None` when
/// there is no such row.
pub(super) fn null_value_key_mask(value_keys: &[ArrayRef]) -> Option<BooleanArray> {

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.

Nit: null_value_key_mask and the NullValueBuildRows fields are only used inside this module now, so they can drop pub(super). Also, HashJoinExec::null_aware_mode() and the null_aware: Option<NotIn> parameters in collect_left_input / SharedBuildAccumulator mix the two names. Renaming them would make it easier to grep for not_in.

@alamb alamb 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.

Thank you @jayzhan211 and @viirya

@jayzhan211
jayzhan211 force-pushed the hj-not-in/1-move-helpers branch from c0d59c7 to a9d4896 Compare October 24, 2026 01:56
@jayzhan211

Copy link
Copy Markdown
Contributor Author

run benchmark hj null_aware_join tpch10

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6378483016-3813-7jqnh 6.12.105+ #1 SMP Sat Sep 5 07:54:11 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing hj-not-in/1-move-helpers (a9d4896) to 702803d (merge-base) diff

Run configuration
run benchmark hj

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6378483016-3815-msfbk 6.12.105+ #1 SMP Sat Sep 5 07:54:11 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing hj-not-in/1-move-helpers (a9d4896) to 702803d (merge-base) diff

Run configuration
run benchmark tpch10

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark running (GKE) | trigger
Instance: c4a-highmem-16 (12 vCPU / 65 GiB) | Linux bench-c6378483016-3814-mm8tb 6.12.105+ #1 SMP Sat Sep 5 07:54:11 UTC 2026 aarch64 GNU/Linux

CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected

Comparing hj-not-in/1-move-helpers (a9d4896) to 702803d (merge-base) diff

Run configuration
run benchmark null_aware_join

Results will be posted here when complete


File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing hj-not-in/1-move-helpers (a9d4896) to 702803d (merge-base) diff

Run configuration
run benchmark hj
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and hj-not-in_1-move-helpers
--------------------
Benchmark hj.json
--------------------
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query                                                                         ┃      HEAD ┃ hj-not-in_1-move-helpers ┃       Change ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 1_density=1_prob_hit=1_25*1.5M                                         │   2.00 ms │                  1.95 ms │    no change │
│ QQuery 2_density=0.026_prob_hit=1_25*1.5M                                     │   4.28 ms │                  4.37 ms │    no change │
│ QQuery 3_density=1_prob_hit=1_100K*60M                                        │  81.31 ms │                 82.00 ms │    no change │
│ QQuery 4_density=1_prob_hit=0.1_100K*60M                                      │ 195.55 ms │                196.15 ms │    no change │
│ QQuery 5_density=0.75_prob_hit=1_100K*60M                                     │ 707.56 ms │                709.96 ms │    no change │
│ QQuery 6_density=0.75_prob_hit=0.1_100K*60M                                   │ 283.41 ms │                283.31 ms │    no change │
│ QQuery 7_density=0.5_prob_hit=1_100K*60M                                      │ 710.50 ms │                682.64 ms │    no change │
│ QQuery 8_density=0.5_prob_hit=0.1_100K*60M                                    │ 258.24 ms │                257.90 ms │    no change │
│ QQuery 9_density=0.2_prob_hit=1_100K*60M                                      │ 718.41 ms │                705.61 ms │    no change │
│ QQuery 10_density=0.2_prob_hit=0.1_100K*60M                                   │ 253.50 ms │                262.72 ms │    no change │
│ QQuery 11_density=0.1_prob_hit=1_100K*60M                                     │ 706.08 ms │                722.54 ms │    no change │
│ QQuery 12_density=0.1_prob_hit=0.1_100K*60M                                   │ 260.69 ms │                265.46 ms │    no change │
│ QQuery 13_density=0.01_prob_hit=1_100K*60M                                    │ 735.61 ms │                733.93 ms │    no change │
│ QQuery 14_density=0.01_prob_hit=0.1_100K*60M                                  │ 278.00 ms │                310.39 ms │ 1.12x slower │
│ QQuery 15_density=0.2_prob_hit=0.1_100K_(20%_dups)*60M                        │ 256.53 ms │                260.62 ms │    no change │
│ QQuery 16_density=1_prob_hit=1_25*1.5M_RightSemi                              │   2.48 ms │                  2.45 ms │    no change │
│ QQuery 17_density=1_prob_hit=1_100K*60M_RightSemi                             │  87.36 ms │                 85.89 ms │    no change │
│ QQuery 18_density=1_prob_hit=0.1_100K*60M_RightSemi                           │ 105.07 ms │                102.54 ms │    no change │
│ QQuery 19_density=1_prob_hit=1_25*1.5M_RightAnti                              │   2.00 ms │                  1.93 ms │    no change │
│ QQuery 20_density=1_prob_hit=1_100K*60M_RightAnti                             │  79.48 ms │                 77.68 ms │    no change │
│ QQuery 21_density=1_prob_hit=0.1_100K*60M_RightAnti                           │ 115.59 ms │                113.11 ms │    no change │
│ QQuery 22_density=1_prob_hit=0.01_100K_(fanout_100)*60M_RightSemi             │  90.13 ms │                 87.76 ms │    no change │
│ QQuery 23_density=1_prob_hit=1_32K_(fanout~78)*2.3M_long_keys_count           │ 166.92 ms │                167.16 ms │    no change │
│ QQuery 24_density=1_prob_hit=1_3K_(single_hot_bucket)*20K_long_keys_count     │ 532.79 ms │                528.04 ms │    no change │
│ QQuery 25_density=1_prob_hit=1_20K_(fanout~78_multi_key)*240K_multi_key_count │ 236.78 ms │                239.35 ms │    no change │
└───────────────────────────────────────────────────────────────────────────────┴───────────┴──────────────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 6870.30ms │
│ Total Time (hj-not-in_1-move-helpers)   │ 6885.46ms │
│ Average Time (HEAD)                     │  274.81ms │
│ Average Time (hj-not-in_1-move-helpers) │  275.42ms │
│ Queries Faster                          │         0 │
│ Queries Slower                          │         1 │
│ Queries with No Change                  │        24 │
│ Queries with Failure                    │         0 │
└─────────────────────────────────────────┴───────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and hj-not-in_1-move-helpers
--------------------
Benchmark hj.json
--------------------
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query                                                                         ┃                               HEAD ┃           hj-not-in_1-move-helpers ┃        Change ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1_density=1_prob_hit=1_25*1.5M                                         │        2.00 / 2.32 ±0.58 / 3.49 ms │        1.95 / 2.27 ±0.44 / 3.14 ms │     no change │
│ QQuery 2_density=0.026_prob_hit=1_25*1.5M                                     │        4.28 / 4.49 ±0.13 / 4.61 ms │        4.37 / 4.55 ±0.13 / 4.72 ms │     no change │
│ QQuery 3_density=1_prob_hit=1_100K*60M                                        │     81.31 / 83.17 ±1.16 / 84.65 ms │     82.00 / 82.72 ±0.41 / 83.21 ms │     no change │
│ QQuery 4_density=1_prob_hit=0.1_100K*60M                                      │ 195.55 / 206.31 ±11.43 / 227.72 ms │  196.15 / 206.89 ±8.28 / 219.78 ms │     no change │
│ QQuery 5_density=0.75_prob_hit=1_100K*60M                                     │ 707.56 / 724.10 ±21.31 / 764.50 ms │ 709.96 / 721.34 ±10.93 / 737.78 ms │     no change │
│ QQuery 6_density=0.75_prob_hit=0.1_100K*60M                                   │ 283.41 / 298.70 ±15.01 / 321.80 ms │ 283.31 / 303.40 ±19.71 / 328.24 ms │     no change │
│ QQuery 7_density=0.5_prob_hit=1_100K*60M                                      │  710.50 / 724.97 ±8.12 / 733.31 ms │ 682.64 / 698.72 ±16.96 / 726.86 ms │     no change │
│ QQuery 8_density=0.5_prob_hit=0.1_100K*60M                                    │ 258.24 / 282.94 ±32.52 / 341.69 ms │ 257.90 / 290.07 ±27.04 / 327.21 ms │     no change │
│ QQuery 9_density=0.2_prob_hit=1_100K*60M                                      │ 718.41 / 732.67 ±15.82 / 762.23 ms │ 705.61 / 726.90 ±13.96 / 745.72 ms │     no change │
│ QQuery 10_density=0.2_prob_hit=0.1_100K*60M                                   │ 253.50 / 280.22 ±17.83 / 302.44 ms │ 262.72 / 280.18 ±18.87 / 307.07 ms │     no change │
│ QQuery 11_density=0.1_prob_hit=1_100K*60M                                     │  706.08 / 712.71 ±5.03 / 720.28 ms │ 722.54 / 755.00 ±31.93 / 809.51 ms │  1.06x slower │
│ QQuery 12_density=0.1_prob_hit=0.1_100K*60M                                   │ 260.69 / 282.79 ±15.55 / 305.55 ms │ 265.46 / 289.77 ±21.83 / 324.51 ms │     no change │
│ QQuery 13_density=0.01_prob_hit=1_100K*60M                                    │ 735.61 / 751.67 ±10.90 / 768.89 ms │ 733.93 / 750.46 ±20.33 / 790.49 ms │     no change │
│ QQuery 14_density=0.01_prob_hit=0.1_100K*60M                                  │ 278.00 / 328.04 ±36.84 / 380.26 ms │ 310.39 / 330.80 ±23.07 / 372.49 ms │     no change │
│ QQuery 15_density=0.2_prob_hit=0.1_100K_(20%_dups)*60M                        │ 256.53 / 285.92 ±31.40 / 339.91 ms │ 260.62 / 308.76 ±41.86 / 366.85 ms │  1.08x slower │
│ QQuery 16_density=1_prob_hit=1_25*1.5M_RightSemi                              │        2.48 / 2.87 ±0.44 / 3.72 ms │        2.45 / 2.53 ±0.05 / 2.59 ms │ +1.13x faster │
│ QQuery 17_density=1_prob_hit=1_100K*60M_RightSemi                             │     87.36 / 89.10 ±1.37 / 90.50 ms │  85.89 / 100.37 ±27.57 / 155.51 ms │  1.13x slower │
│ QQuery 18_density=1_prob_hit=0.1_100K*60M_RightSemi                           │ 105.07 / 114.10 ±10.17 / 127.21 ms │  102.54 / 105.81 ±3.88 / 113.29 ms │ +1.08x faster │
│ QQuery 19_density=1_prob_hit=1_25*1.5M_RightAnti                              │        2.00 / 2.12 ±0.12 / 2.31 ms │        1.93 / 2.04 ±0.11 / 2.26 ms │     no change │
│ QQuery 20_density=1_prob_hit=1_100K*60M_RightAnti                             │     79.48 / 80.92 ±1.64 / 84.02 ms │     77.68 / 78.26 ±0.53 / 79.08 ms │     no change │
│ QQuery 21_density=1_prob_hit=0.1_100K*60M_RightAnti                           │  115.59 / 116.58 ±0.61 / 117.19 ms │  113.11 / 113.66 ±0.47 / 114.53 ms │     no change │
│ QQuery 22_density=1_prob_hit=0.01_100K_(fanout_100)*60M_RightSemi             │     90.13 / 91.02 ±0.52 / 91.77 ms │     87.76 / 89.19 ±0.89 / 90.08 ms │     no change │
│ QQuery 23_density=1_prob_hit=1_32K_(fanout~78)*2.3M_long_keys_count           │  166.92 / 168.22 ±1.93 / 172.04 ms │  167.16 / 167.59 ±0.33 / 168.12 ms │     no change │
│ QQuery 24_density=1_prob_hit=1_3K_(single_hot_bucket)*20K_long_keys_count     │  532.79 / 536.07 ±4.00 / 543.67 ms │  528.04 / 530.10 ±1.66 / 532.54 ms │     no change │
│ QQuery 25_density=1_prob_hit=1_20K_(fanout~78_multi_key)*240K_multi_key_count │  236.78 / 238.50 ±1.44 / 240.73 ms │  239.35 / 240.46 ±1.66 / 243.75 ms │     no change │
└───────────────────────────────────────────────────────────────────────────────┴────────────────────────────────────┴────────────────────────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 7140.52ms │
│ Total Time (hj-not-in_1-move-helpers)   │ 7181.84ms │
│ Average Time (HEAD)                     │  285.62ms │
│ Average Time (hj-not-in_1-move-helpers) │  287.27ms │
│ Queries Faster                          │         2 │
│ Queries Slower                          │         3 │
│ Queries with No Change                  │        20 │
│ Queries with Failure                    │         0 │
└─────────────────────────────────────────┴───────────┘

Resource Usage

hj — base (merge-base)

Metric Value
Wall time 40.0s
Peak memory 2.6 GiB
Avg memory 1.2 GiB
CPU user 333.2s
CPU sys 12.1s
Peak spill 0 B

hj — branch

Metric Value
Wall time 40.0s
Peak memory 2.9 GiB
Avg memory 1.2 GiB
CPU user 332.7s
CPU sys 12.3s
Peak spill 0 B

File an issue against this benchmark runner

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing hj-not-in/1-move-helpers (a9d4896) to 702803d (merge-base) diff

Run configuration
run benchmark tpch10
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

Comparing HEAD and hj-not-in_1-move-helpers
--------------------
Benchmark tpch_sf10.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query     ┃       HEAD ┃ hj-not-in_1-move-helpers ┃    Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 1  │  340.71 ms │                336.78 ms │ no change │
│ QQuery 2  │   93.61 ms │                 94.51 ms │ no change │
│ QQuery 3  │  228.19 ms │                226.92 ms │ no change │
│ QQuery 4  │  119.38 ms │                119.91 ms │ no change │
│ QQuery 5  │  359.49 ms │                375.17 ms │ no change │
│ QQuery 6  │  107.35 ms │                104.66 ms │ no change │
│ QQuery 7  │  488.64 ms │                492.12 ms │ no change │
│ QQuery 8  │  370.71 ms │                371.59 ms │ no change │
│ QQuery 9  │  556.88 ms │                541.24 ms │ no change │
│ QQuery 10 │  303.19 ms │                297.84 ms │ no change │
│ QQuery 11 │   64.01 ms │                 66.71 ms │ no change │
│ QQuery 12 │  124.07 ms │                125.94 ms │ no change │
│ QQuery 13 │  324.80 ms │                327.13 ms │ no change │
│ QQuery 14 │  178.26 ms │                175.64 ms │ no change │
│ QQuery 15 │  315.57 ms │                318.58 ms │ no change │
│ QQuery 16 │   66.76 ms │                 64.96 ms │ no change │
│ QQuery 17 │  676.62 ms │                654.22 ms │ no change │
│ QQuery 18 │ 1065.48 ms │               1044.60 ms │ no change │
│ QQuery 19 │  241.90 ms │                239.78 ms │ no change │
│ QQuery 20 │  282.90 ms │                295.89 ms │ no change │
│ QQuery 21 │  707.53 ms │                711.24 ms │ no change │
│ QQuery 22 │   62.03 ms │                 63.82 ms │ no change │
└───────────┴────────────┴──────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 7078.10ms │
│ Total Time (hj-not-in_1-move-helpers)   │ 7049.25ms │
│ Average Time (HEAD)                     │  321.73ms │
│ Average Time (hj-not-in_1-move-helpers) │  320.42ms │
│ Queries Faster                          │         0 │
│ Queries Slower                          │         0 │
│ Queries with No Change                  │        22 │
│ Queries with Failure                    │         0 │
└─────────────────────────────────────────┴───────────┘

Distribution per query (min / mean ±stddev / max):

Comparing HEAD and hj-not-in_1-move-helpers
--------------------
Benchmark tpch_sf10.json
--------------------
┏━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Query     ┃                                  HEAD ┃              hj-not-in_1-move-helpers ┃    Change ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ QQuery 1  │     340.71 / 343.19 ±2.25 / 346.35 ms │     336.78 / 341.99 ±3.93 / 345.79 ms │ no change │
│ QQuery 2  │        93.61 / 95.30 ±1.11 / 96.50 ms │        94.51 / 96.97 ±1.64 / 99.37 ms │ no change │
│ QQuery 3  │     228.19 / 233.53 ±4.27 / 240.73 ms │     226.92 / 233.12 ±5.30 / 239.91 ms │ no change │
│ QQuery 4  │     119.38 / 120.46 ±1.03 / 122.33 ms │     119.91 / 120.65 ±0.72 / 121.69 ms │ no change │
│ QQuery 5  │    359.49 / 379.21 ±12.34 / 396.79 ms │    375.17 / 386.08 ±11.10 / 406.56 ms │ no change │
│ QQuery 6  │     107.35 / 110.85 ±2.87 / 115.97 ms │     104.66 / 108.48 ±3.97 / 115.35 ms │ no change │
│ QQuery 7  │    488.64 / 503.56 ±11.43 / 517.88 ms │    492.12 / 513.58 ±12.43 / 530.82 ms │ no change │
│ QQuery 8  │     370.71 / 376.74 ±3.63 / 381.65 ms │     371.59 / 374.94 ±2.57 / 378.54 ms │ no change │
│ QQuery 9  │    556.88 / 592.41 ±27.09 / 639.16 ms │    541.24 / 565.47 ±16.28 / 590.79 ms │ no change │
│ QQuery 10 │     303.19 / 313.19 ±7.90 / 322.72 ms │     297.84 / 309.36 ±6.24 / 316.44 ms │ no change │
│ QQuery 11 │        64.01 / 71.47 ±9.55 / 90.34 ms │        66.71 / 72.36 ±8.42 / 89.11 ms │ no change │
│ QQuery 12 │     124.07 / 128.48 ±7.44 / 143.33 ms │     125.94 / 130.08 ±5.56 / 141.09 ms │ no change │
│ QQuery 13 │    324.80 / 338.03 ±10.22 / 353.04 ms │     327.13 / 330.82 ±3.12 / 335.25 ms │ no change │
│ QQuery 14 │     178.26 / 183.90 ±4.32 / 190.73 ms │     175.64 / 184.51 ±8.34 / 199.88 ms │ no change │
│ QQuery 15 │     315.57 / 320.36 ±3.26 / 324.50 ms │     318.58 / 320.93 ±2.22 / 324.53 ms │ no change │
│ QQuery 16 │        66.76 / 69.16 ±2.78 / 73.83 ms │        64.96 / 68.68 ±2.82 / 72.73 ms │ no change │
│ QQuery 17 │     676.62 / 687.97 ±9.23 / 701.97 ms │    654.22 / 675.70 ±17.41 / 699.26 ms │ no change │
│ QQuery 18 │ 1065.48 / 1106.00 ±27.18 / 1146.93 ms │ 1044.60 / 1080.50 ±34.59 / 1127.44 ms │ no change │
│ QQuery 19 │    241.90 / 265.04 ±35.06 / 334.46 ms │    239.78 / 265.92 ±30.14 / 322.46 ms │ no change │
│ QQuery 20 │    282.90 / 294.26 ±10.46 / 312.63 ms │     295.89 / 299.45 ±3.78 / 306.41 ms │ no change │
│ QQuery 21 │    707.53 / 720.19 ±10.35 / 737.85 ms │    711.24 / 722.02 ±11.86 / 743.42 ms │ no change │
│ QQuery 22 │        62.03 / 66.69 ±3.60 / 72.96 ms │        63.82 / 66.23 ±1.70 / 68.14 ms │ no change │
└───────────┴───────────────────────────────────────┴───────────────────────────────────────┴───────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary                       ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)                       │ 7320.01ms │
│ Total Time (hj-not-in_1-move-helpers)   │ 7267.85ms │
│ Average Time (HEAD)                     │  332.73ms │
│ Average Time (hj-not-in_1-move-helpers) │  330.36ms │
│ Queries Faster                          │         0 │
│ Queries Slower                          │         0 │
│ Queries with No Change                  │        22 │
│ Queries with Failure                    │         0 │
└─────────────────────────────────────────┴───────────┘

Resource Usage

tpch10 — base (merge-base)

Metric Value
Wall time 40.0s
Peak memory 12.1 GiB
Avg memory 2.6 GiB
CPU user 368.0s
CPU sys 24.9s
Peak spill 0 B

tpch10 — branch

Metric Value
Wall time 40.0s
Peak memory 14.6 GiB
Avg memory 2.9 GiB
CPU user 364.9s
CPU sys 24.5s
Peak spill 0 B

File an issue against this benchmark runner

];

let mut results = vec![];
for join_type in [JoinType::LeftAnti, JoinType::LeftMark] {

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.

after_empty_build is new here and only matters for a correlated RightAnti, which this loop doesn't run. Would it be worth adding JoinType::RightAnti (with the all-NULL table still as the build side) and one more probe row in an empty scope, e.g. (9, 4)? That leaves the LeftAnti/LeftMark expectations unchanged, and RightAnti should emit only (9, 4). If after_empty_build stopped filtering, (5, 1) and (7, 2) would come back. As far as I can tell only the fuzz test reaches that path today.

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.

Thanks. Putting (9, 4) in the same batch as (7, 2) is better than what I suggested: it also checks that the empty-build path drops rows, not whole batches.


// Kept out of line: inlined into `HashJoinStream::process_probe_batch`,
// this loop measured ~4-6% slower on high-fanout probes.
#[inline(never)]

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.

Thanks for taking them out. The bot's hj run on a9d4896 shows Q22 and Q24 unchanged (both slightly faster at the minimum), and the few flagged queries (Q11/Q14/Q15/Q17 slower, Q16/Q18 faster) look like run-to-run variance given their stddevs; tpch10 is flat. So the earlier regression doesn't reproduce on this aarch64 runner without the pins. Once null_aware_join is in too, this looks good to me without them.

/// After a lookup: [`NotIn::ProbeRows`] drops the probe rows whose key is
/// NULL, since `NULL NOT IN (..)` is never TRUE. `valid_keys` is the probe
/// keys' validity (`matchable_join_keys`), when the batch has one.
pub(super) fn after_lookup(

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.

Makes sense. With unknown_probe_rows living in NotInProbe, keeping it there reads fine.

@adriangbot

Copy link
Copy Markdown

🤖 Benchmark completed (GKE) | trigger

Instance: c4a-highmem-16 (12 vCPU / 65 GiB)

Comparing hj-not-in/1-move-helpers (a9d4896) to 702803d (merge-base) diff

Run configuration
run benchmark null_aware_join
CPU Details (lscpu)
Architecture:                            aarch64
CPU op-mode(s):                          64-bit
Byte Order:                              Little Endian
CPU(s):                                  16
On-line CPU(s) list:                     0-15
Vendor ID:                               ARM
Model name:                              Neoverse-V2
Model:                                   1
Thread(s) per core:                      1
Core(s) per cluster:                     16
Socket(s):                               -
Cluster(s):                              1
Stepping:                                r0p1
BogoMIPS:                                2000.00
Flags:                                   fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti
L1d cache:                               1 MiB (16 instances)
L1i cache:                               1 MiB (16 instances)
L2 cache:                                32 MiB (16 instances)
L3 cache:                                80 MiB (1 instance)
NUMA node(s):                            1
NUMA node0 CPU(s):                       0-15
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; CSV2, BHB
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Not affected
Details

group                  HEAD                                   hj-not-in_1-move-helpers
-----                  ----                                   ------------------------
null_aware_join/Q01    1.04      4.9±0.12ms        ? ?/sec    1.00      4.7±0.07ms        ? ?/sec
null_aware_join/Q02    1.02      5.8±0.11ms        ? ?/sec    1.00      5.7±0.13ms        ? ?/sec
null_aware_join/Q03    1.05      5.3±0.09ms        ? ?/sec    1.00      5.1±0.20ms        ? ?/sec
null_aware_join/Q04    1.00    778.1±7.63µs        ? ?/sec    1.00   781.2±10.18µs        ? ?/sec
null_aware_join/Q05    1.02  1313.9±21.01µs        ? ?/sec    1.00  1285.7±12.59µs        ? ?/sec
null_aware_join/Q06    1.02  1294.6±22.05µs        ? ?/sec    1.00  1271.6±11.51µs        ? ?/sec
null_aware_join/Q07    1.01  1079.2±15.19µs        ? ?/sec    1.00  1073.5±20.20µs        ? ?/sec
null_aware_join/Q08    1.00     20.4±0.06ms        ? ?/sec    1.02     20.8±0.06ms        ? ?/sec
null_aware_join/Q09    1.01  1053.5±11.53µs        ? ?/sec    1.00  1044.7±15.17µs        ? ?/sec
null_aware_join/Q10    1.00    798.5±8.97µs        ? ?/sec    1.00    800.2±9.99µs        ? ?/sec
null_aware_join/Q11    1.00      6.3±0.04ms        ? ?/sec    1.00      6.3±0.07ms        ? ?/sec
null_aware_join/Q12    1.00  1206.9±10.62µs        ? ?/sec    1.00   1204.4±8.87µs        ? ?/sec

Resource Usage

null_aware_join — base (merge-base)

Metric Value
Wall time 860.4s
Peak memory 331.2 MiB
Avg memory 39.1 MiB
CPU user 244.4s
CPU sys 16.8s
Peak spill 0 B

null_aware_join — branch

Metric Value
Wall time 855.4s
Peak memory 318.8 MiB
Avg memory 39.2 MiB
CPU user 240.7s
CPU sys 17.2s
Peak spill 0 B

File an issue against this benchmark runner

@jayzhan211
jayzhan211 requested a review from viirya October 24, 2026 03:06

@viirya viirya 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.

Thanks for the follow-ups, LGTM.

];

let mut results = vec![];
for join_type in [JoinType::LeftAnti, JoinType::LeftMark] {

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.

Thanks. Putting (9, 4) in the same batch as (7, 2) is better than what I suggested: it also checks that the empty-build path drops rows, not whole batches.


// Kept out of line: inlined into `HashJoinStream::process_probe_batch`,
// this loop measured ~4-6% slower on high-fanout probes.
#[inline(never)]

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.

null_aware_join is in as well: 11 of 12 queries are unchanged or faster. Q08 is 2% slower (20.4 → 20.8 ms), but Q06, with the same NULL fraction and no scope key, is 2% faster, and Q08's marking path is moved unchanged, so I read it as code placement. I'm fine leaving the pins out.

@jayzhan211
jayzhan211 added this pull request to the merge queue Oct 24, 2026
@jayzhan211

Copy link
Copy Markdown
Contributor Author

Thanks @viirya @alamb !

Merged via the queue into apache:main with commit a7787f4 Oct 24, 2026
46 checks passed
@jayzhan211
jayzhan211 deleted the hj-not-in/1-move-helpers branch October 24, 2026 05:46
@adriangb

Copy link
Copy Markdown
Contributor

Thanks for getting this done folks, sorry I couldn't get to it!

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

Labels

core Core DataFusion crate optimizer Optimizer rules physical-plan Changes to the physical-plan crate v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants