Repository navigation
Commit 1fa378d
authored
perf: keep the hash join unique-key fast path when the build side has NULL keys (#26018)
## Which issue does this PR close?
- None. There is no separate issue; the problem is described below.
## Rationale for this change
When every build key is unique, the hash join probe uses a faster loop
that skips walking the duplicate-key chain. That loop was enabled by
`map.len() == next.len()`, which means "every build row is in the map".
Under `NullEqualsNothing`, build rows with a NULL key are not inserted,
because they can never match. So a single NULL build key turns the fast
path off, and every probe walks the chain even though every chain has
length 1.
This affects the hash-map path: string keys, multi-column keys and
wide-range integer keys. It does not affect `ArrayMap`. A nullable
unique key on the build side is enough to trigger it.
## What changes are included in this PR?
- `update_from_iter` returns whether it chained a row onto an existing
hash value. `JoinHashMapU32` and `JoinHashMapU64` keep that result in a
`chained` flag across batches.
- `get_matched_indices_with_limit_offset` now takes the uniqueness
decision as a parameter instead of computing `map.len() ==
next_chain.len()` itself. The two join hash maps pass `!chained`.
`PruningJoinHashMap` (symmetric hash join) still passes `map.len() ==
next.len()`, so its behavior is unchanged.
- Nothing changes when no build rows are skipped, because `map.len() ==
next.len()` held exactly when no row was chained. `NullEqualsNull` is
also unchanged: NULL rows are inserted and chain as before.
## What is the testing strategy for this PR?
New unit tests:
- `join_hash_map::tests::test_unique_fast_path_with_uninserted_row`:
unique keys plus one row that was not inserted now take the fast path.
The test checks the fast path's offset behavior with `limit = 2`; before
this change it failed with `Some((1, Some(0)))`.
-
`join_hash_map::tests::test_chain_from_earlier_update_disables_fast_path`:
a chain created by an earlier batch keeps the chain walk.
- `utils::tests::update_hash_null_key_does_not_change_matches`:
`update_hash` plus a chunked probe give the same matches with and
without a NULL build key.
Benchmark: `datafusion-cli` built with `release-nonlto`, base vs. this
PR, on an Apple M4 Pro (12 cores, 24 GB) with default
`target_partitions`. Each query is `SELECT count(*) FROM probe p JOIN
build b ON <keys>`. The build side has 5M unique keys (`Utf8View`, or
`(Int64 wide range, Utf8View)`) plus one NULL-key row. The probe side
has 20M rows in shuffled order and matches every key. Runs were
interleaved; each value is the median of 18 runs (6 rounds × 3), started
only when the 1-minute load average was below 4. Under default
thresholds the 5M-row build side runs in `Partitioned` mode. The
`CollectLeft` rows raise `hash_join_single_partition_threshold{,_rows}`
so that the whole build side is one map. `EXPLAIN ANALYZE` shows
`array_map_created_count=0` in every case except where noted.
| Case | Mode | base (s) | PR (s) | PR/base |
|---|---|---|---|---|
| Utf8View key, unique + 1 NULL | CollectLeft | 0.237 | 0.214 | **0.90**
|
| (Int64, Utf8View) key, unique + 1 NULL | CollectLeft | 0.284 | 0.259 |
**0.91** |
| Utf8View key, unique + 1 NULL | Partitioned | 0.144 | 0.143 | 0.99 |
| (Int64, Utf8View) key, unique + 1 NULL | Partitioned | 0.192 | 0.189 |
0.99 |
| Control: Utf8View key, unique, no NULL | Partitioned | 0.134 | 0.137 |
1.02 |
| Control: Utf8View key, unique, no NULL | CollectLeft | 0.205 | 0.210 |
1.03 |
| Control: (Int64, Utf8View) key, unique, no NULL | CollectLeft | 0.253
| 0.253 | 1.00 |
| Control: 2 rows per key + 1 NULL | Partitioned | 0.163 | 0.161 | 0.99
|
| Control: 2 rows per key + 1 NULL | CollectLeft | 0.260 | 0.264 | 1.02
|
| Control: `hj` Q13 (TPC-H SF10, 1% density, hash map), `count(*)` |
CollectLeft | 0.067 | 0.068 | 1.02 |
`HashJoinExec` `join_time` from `EXPLAIN ANALYZE` (summed over
partitions, median of 7 runs):
| Case | base | PR |
|---|---|---|
| Utf8View key + 1 NULL, CollectLeft | 1.14 s | 0.84 s (-26%) |
| (Int64, Utf8View) key + 1 NULL, CollectLeft | 1.31 s | 1.01 s (-23%) |
| Utf8View key + 1 NULL, Partitioned | 729 ms | 715 ms |
| Control: no NULL, CollectLeft | 802 ms | 809 ms |
| Control: 2 rows per key, CollectLeft | 1.15 s | 1.12 s |
With one NULL key, `join_time` now matches the no-NULL control. In
`Partitioned` mode all NULL-key build rows hash to a single partition,
so only that partition lost the fast path, and the end-to-end change
there is within noise. Run-to-run spread on this shared machine is about
±3%, which covers every control.
## Are there any user-facing changes?
No. `join_hash_map` is `#[doc(hidden)]` internal API, although the
signatures of its free functions `update_from_iter` and
`get_matched_indices_with_limit_offset` change.1 parent a67d64a commit 1fa378d
3 files changed
Lines changed: 199 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| 149 | + | |
| 150 | + | |
149 | 151 | | |
150 | 152 | | |
151 | 153 | | |
152 | 154 | | |
153 | 155 | | |
154 | | - | |
| 156 | + | |
| 157 | + | |
155 | 158 | | |
156 | 159 | | |
157 | 160 | | |
158 | 161 | | |
159 | 162 | | |
160 | 163 | | |
| 164 | + | |
161 | 165 | | |
162 | 166 | | |
163 | 167 | | |
| |||
176 | 180 | | |
177 | 181 | | |
178 | 182 | | |
179 | | - | |
| 183 | + | |
| 184 | + | |
180 | 185 | | |
181 | 186 | | |
182 | 187 | | |
| |||
199 | 204 | | |
200 | 205 | | |
201 | 206 | | |
| 207 | + | |
202 | 208 | | |
203 | 209 | | |
204 | 210 | | |
| |||
226 | 232 | | |
227 | 233 | | |
228 | 234 | | |
| 235 | + | |
| 236 | + | |
229 | 237 | | |
230 | 238 | | |
231 | 239 | | |
232 | 240 | | |
233 | 241 | | |
234 | | - | |
| 242 | + | |
| 243 | + | |
235 | 244 | | |
236 | 245 | | |
237 | 246 | | |
238 | 247 | | |
239 | 248 | | |
240 | 249 | | |
| 250 | + | |
241 | 251 | | |
242 | 252 | | |
243 | 253 | | |
| |||
256 | 266 | | |
257 | 267 | | |
258 | 268 | | |
259 | | - | |
| 269 | + | |
| 270 | + | |
260 | 271 | | |
261 | 272 | | |
262 | 273 | | |
| |||
279 | 290 | | |
280 | 291 | | |
281 | 292 | | |
| 293 | + | |
282 | 294 | | |
283 | 295 | | |
284 | 296 | | |
| |||
304 | 316 | | |
305 | 317 | | |
306 | 318 | | |
| 319 | + | |
| 320 | + | |
307 | 321 | | |
308 | 322 | | |
309 | 323 | | |
310 | 324 | | |
311 | 325 | | |
312 | | - | |
| 326 | + | |
| 327 | + | |
313 | 328 | | |
314 | 329 | | |
315 | 330 | | |
| 331 | + | |
316 | 332 | | |
317 | 333 | | |
318 | 334 | | |
| |||
329 | 345 | | |
330 | 346 | | |
331 | 347 | | |
| 348 | + | |
332 | 349 | | |
333 | 350 | | |
334 | 351 | | |
335 | 352 | | |
336 | 353 | | |
337 | 354 | | |
| 355 | + | |
338 | 356 | | |
339 | 357 | | |
340 | 358 | | |
| |||
385 | 403 | | |
386 | 404 | | |
387 | 405 | | |
| 406 | + | |
| 407 | + | |
388 | 408 | | |
389 | 409 | | |
390 | 410 | | |
391 | 411 | | |
| 412 | + | |
392 | 413 | | |
393 | 414 | | |
394 | 415 | | |
| |||
408 | 429 | | |
409 | 430 | | |
410 | 431 | | |
411 | | - | |
| 432 | + | |
412 | 433 | | |
413 | 434 | | |
414 | 435 | | |
| |||
569 | 590 | | |
570 | 591 | | |
571 | 592 | | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
572 | 698 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
| 95 | + | |
95 | 96 | | |
96 | 97 | | |
97 | 98 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2864 | 2864 | | |
2865 | 2865 | | |
2866 | 2866 | | |
| 2867 | + | |
| 2868 | + | |
| 2869 | + | |
| 2870 | + | |
| 2871 | + | |
| 2872 | + | |
| 2873 | + | |
| 2874 | + | |
| 2875 | + | |
| 2876 | + | |
| 2877 | + | |
| 2878 | + | |
| 2879 | + | |
| 2880 | + | |
| 2881 | + | |
| 2882 | + | |
| 2883 | + | |
| 2884 | + | |
| 2885 | + | |
| 2886 | + | |
| 2887 | + | |
| 2888 | + | |
| 2889 | + | |
| 2890 | + | |
| 2891 | + | |
| 2892 | + | |
| 2893 | + | |
| 2894 | + | |
| 2895 | + | |
| 2896 | + | |
| 2897 | + | |
| 2898 | + | |
| 2899 | + | |
| 2900 | + | |
| 2901 | + | |
| 2902 | + | |
| 2903 | + | |
| 2904 | + | |
| 2905 | + | |
| 2906 | + | |
| 2907 | + | |
| 2908 | + | |
| 2909 | + | |
| 2910 | + | |
| 2911 | + | |
| 2912 | + | |
| 2913 | + | |
| 2914 | + | |
| 2915 | + | |
| 2916 | + | |
| 2917 | + | |
| 2918 | + | |
| 2919 | + | |
| 2920 | + | |
| 2921 | + | |
| 2922 | + | |
| 2923 | + | |
| 2924 | + | |
| 2925 | + | |
| 2926 | + | |
| 2927 | + | |
| 2928 | + | |
| 2929 | + | |
| 2930 | + | |
| 2931 | + | |
| 2932 | + | |
2867 | 2933 | | |
2868 | 2934 | | |
2869 | 2935 | | |
| |||
0 commit comments