Skip to content

Conversation

@adriangb
Copy link
Contributor

@adriangb adriangb commented Nov 2, 2025

Background

This PR is part of an EPIC to push down hash table references from HashJoinExec into scans. The EPIC is tracked in #17171.

A "target state" is tracked in #18393.
There is a series of PRs to get us to this target state in smaller more reviewable changes that are still valuable on their own:

Changes in this PR

  • Enhance InListExpr to efficiently store homogeneous lists as arrays and avoid a conversion to Vec
    by adding an internal InListStorage enum with Array and Exprs variants
  • Re-use existing hashing and comparison utilities to support Struct arrays and other complex types
  • Add public function in_list_from_array(expr, list_array, negated) for creating InList from arrays

Although the diff looks large most of it is actually tests and docs. I think the actual code change is a negative LOC change, or at least negative complexity (eliminates a trait, a macro, matching on data types).

@github-actions github-actions bot added physical-expr Changes to the physical-expr crates sqllogictest SQL Logic Tests (.slt) common Related to common crate proto Related to proto crate physical-plan Changes to the physical-plan crate labels Nov 2, 2025
Comment on lines 318 to 320
// TODO: serialize the inner ArrayRef directly to avoid materialization into literals
// by extending the protobuf definition to support both representations and adding a public
// accessor method to InListExpr to get the inner ArrayRef
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll create a followup issue once we merge this

05)--------ProjectionExec: expr=[]
06)----------CoalesceBatchesExec: target_batch_size=8192
07)------------FilterExec: substr(md5(CAST(value@0 AS Utf8View)), 1, 32) IN ([7f4b18de3cfeb9b4ac78c381ee2ad278, a, b, c])
07)------------FilterExec: substr(md5(CAST(value@0 AS Utf8View)), 1, 32) IN (SET) ([7f4b18de3cfeb9b4ac78c381ee2ad278, a, b, c])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is because we now support Utf8View for building the sets 😄

Comment on lines 565 to 572
let random_state = RandomState::with_seed(0);
let mut hashes_buf = vec![0u64; array.len()];
let Ok(hashes_buf) = create_hashes_from_arrays(
&[array.as_ref()],
&random_state,
&mut hashes_buf,
) else {
unreachable!("Failed to create hashes for InList array. This shouldn't happen because make_set should have succeeded earlier.");
};
hashes_buf.hash(state);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could pre-compute and store a hash: u64 which would be both more performant when Hash is called and avoid this error, but it would add more complexity and some overhead when building the InListExpr

alamb added a commit to alamb/datafusion that referenced this pull request Nov 7, 2025
## Background

This PR is part of an EPIC to push down hash table references from
HashJoinExec into scans. The EPIC is tracked in
apache#17171.

A "target state" is tracked in
apache#18393.
There is a series of PRs to get us to this target state in smaller more
reviewable changes that are still valuable on their own:
- (This PR): apache#18448
- apache#18449 (depends on
apache#18448)
- apache#18451

## Changes in this PR

Change create_hashes and related functions to work with &dyn Array
references instead of requiring ArrayRef (Arc-wrapped arrays). This
avoids unnecessary Arc::clone() calls and enables calls that only have
an &dyn Array to use the hashing utilities.

- Add create_hashes_from_arrays(&[&dyn Array]) function
- Refactor hash_dictionary, hash_list_array, hash_fixed_list_array to
use references instead of cloning
- Extract hash_single_array() helper for common logic

---------

Co-authored-by: Andrew Lamb <[email protected]>
@github-actions github-actions bot removed common Related to common crate physical-plan Changes to the physical-plan crate labels Nov 7, 2025
/// supported. Returns None otherwise. See [`LiteralGuarantee::analyze`] to
/// create these structures from an predicate (boolean expression).
fn new<'a>(
fn new(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's worth discussing in this review how far we propagate the changes.

In particular, InListExpr will now have two operations modes:

  1. Was built with an ArrayRef or was able to build an ArrayRef from a homogeneously typed Vec<Arc<dyn PhysicalExpr>> which are all literals.
  2. Was built with a Vec<Arc<dyn PhysicalExpr>> which are not literals or homogeneously typed.

If we restrict LiteralGuarantee to only operate on the first cases, I think we could lift out a lot of computation: instead of transforming ArrayRef -> Vec<Arc<dyn PhysicalExpr>> -> Vec<ScalarValue> -> HashSet<ScalarValue> which then gets fed into bloom filters which are per-column and don't really support heterogenous ScalarValues we could re-use the already deduplicated ArraySet that InListExpr has internally or something. The ultimate thing to do, but that would require even more work and changes, would be to make PruningPredicate::contains accept an enum ArrayOrScalars { Array(ArrayRef), Scalars(Vec<ScalarValue>) } so that we can push down and iterate over the Arc'ed ArrayRef the whole way down. I think we could make this backwards compatible.

I think that change is worth it, but it requires a bit more coordination (with arrow-rs) and a bigger change.

The end result would be that:

  1. When you create an InListExpr operates in mode (1) we are able to push down into bloom filters with no data copies at all.
  2. When the InListExpr operates in mode (2) we'd bail on the pushdown early (e.g. list() -> Option<ArrayRef>) and avoid building the HashSet<ScalarValue>, etc. that won't be used.

Wdyt @alamb ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay I've looked into this and it is entirely possible, I think we should do it.
Basically the status quo currently is that we always try to build an ArrayHashSet which is only possible if we can convert the Vec<ScalarValue> into an ArrayRef.

At that point the only reason to store the Vec<SclarValue> is to later pass it into PruningPredicate -> bloom filters and LiteralGuarantee. If we can refactor those two to also handle an ArrayRef we could probably avoid a lot of cloning and make things more efficient by using arrays. I don't even think we need to support Vec<ScalarValue> at all: the only reason to have that is if you could not build a homogeneously typed array, and if that is the case you probably can't do any sort of pushdown into a bloom filter. E.g. select variant_get(col, 'abc') in (1, 2.0, 'c') might make sense and work but I don't think we could ever push that down into a bloom filter. So InListExpr needs to operate on both but I don't think the pruning machinery does.

So anyway I think I may try to reduce this change to only be about using create_hashes and ignore any inefficiencies as a TODO for a followup issue. At the end of the day if we can make HashJoinExec faster even if that's with some inefficiencies I think that's okay and we can improve more later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll record a preview of some of the changes I had made to explore this (by no means ready) just for future reference: https://github.com/pydantic/datafusion/compare/refactor-in-list...pydantic:datafusion:use-array-in-pruning?expand=1

@github-actions github-actions bot removed the proto Related to proto crate label Nov 9, 2025
@adriangb adriangb changed the title Refactor InListExpr to store arrays and support structs Refactor InListExpr to support structs by re-using existing hashing infrastructure Nov 9, 2025
Comment on lines -69 to -72
pub trait Set: Send + Sync {
fn contains(&self, v: &dyn Array, negated: bool) -> Result<BooleanArray>;
fn has_nulls(&self) -> bool;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We get rid of the Set trait. The only implementer was ArraySet

Comment on lines 186 to 190
array => Arc::new(ArraySet::new(array, make_hash_set(array))),
DataType::Boolean => {
let array = as_boolean_array(array)?;
Arc::new(ArraySet::new(array, make_hash_set(array)))
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We get rid of this type matching logic

Comment on lines -243 to -251
trait IsEqual: HashValue {
fn is_equal(&self, other: &Self) -> bool;
}

impl<T: IsEqual + ?Sized> IsEqual for &T {
fn is_equal(&self, other: &Self) -> bool {
T::is_equal(self, other)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We get rid of these custom equality / hash traits

Changes:
- Enhance InListExpr to efficiently store homogeneous lists as arrays and avoid a conversion to Vec<PhysicalExpr>
    by adding an internal InListStorage enum with Array and Exprs variants
- Re-use existing hashing and comparison utilities to support Struct arrays and other complex types
- Add public function `in_list_from_array(expr, list_array, negated)` for creating InList from arrays
@github-actions github-actions bot added the common Related to common crate label Nov 10, 2025
@adriangb
Copy link
Contributor Author

adriangb commented Nov 10, 2025

@alamb could you run the benchmark again? Also tpch would be great since the plans did change (I think for the better).

I'm getting promising results locally:

in_list_utf8(5) (1024, 0) IN (1, 0)
                        time:   [2.1329 µs 2.1369 µs 2.1412 µs]
                        change: [−24.304% −24.100% −23.908%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe

in_list_utf8(10) (1024, 0) IN (1, 0)
                        time:   [2.1459 µs 2.1495 µs 2.1533 µs]
                        change: [−23.928% −23.660% −23.351%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) high mild
  1 (1.00%) high severe

in_list_utf8(20) (1024, 0) IN (1, 0)
                        time:   [2.1438 µs 2.1482 µs 2.1529 µs]
                        change: [−35.000% −32.830% −30.673%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  3 (3.00%) high mild
  1 (1.00%) high severe

in_list_f32 (1024, 0) IN (1, 0)
                        time:   [2.1289 µs 2.1326 µs 2.1368 µs]
                        change: [−38.413% −36.728% −35.228%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe

in_list_i32 (1024, 0) IN (1, 0)
                        time:   [2.1273 µs 2.1322 µs 2.1381 µs]
                        change: [−16.900% −16.602% −16.305%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) low mild
  1 (1.00%) high mild
  1 (1.00%) high severe

in_list_utf8(5) (1024, 0.2) IN (1, 0)
                        time:   [3.0365 µs 3.0428 µs 3.0490 µs]
                        change: [−36.487% −34.532% −32.458%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) high mild
  1 (1.00%) high severe

in_list_utf8(10) (1024, 0.2) IN (1, 0)
                        time:   [3.0444 µs 3.0522 µs 3.0595 µs]
                        change: [−36.231% −34.959% −33.607%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) high mild
  1 (1.00%) high severe

in_list_utf8(20) (1024, 0.2) IN (1, 0)
                        time:   [3.1244 µs 3.1314 µs 3.1385 µs]
                        change: [−11.989% −11.717% −11.422%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
  1 (1.00%) high severe

in_list_f32 (1024, 0.2) IN (1, 0)
                        time:   [2.8616 µs 2.8686 µs 2.8761 µs]
                        change: [−4.6147% −4.1434% −3.6780%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  2 (2.00%) high mild
  3 (3.00%) high severe

in_list_i32 (1024, 0.2) IN (1, 0)
                        time:   [2.8689 µs 2.8800 µs 2.8948 µs]
                        change: [−6.4977% −6.0731% −5.5574%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high severe

in_list_utf8(5) (1024, 0) IN (3, 0)
                        time:   [2.1316 µs 2.1376 µs 2.1439 µs]
                        change: [−24.605% −24.365% −24.166%] (p = 0.00 < 0.05)
                        Performance has improved.

in_list_utf8(10) (1024, 0) IN (3, 0)
                        time:   [2.1284 µs 2.1326 µs 2.1368 µs]
                        change: [−25.190% −24.964% −24.739%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe

in_list_utf8(20) (1024, 0) IN (3, 0)
                        time:   [2.1289 µs 2.1335 µs 2.1384 µs]
                        change: [−24.859% −24.619% −24.366%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe

in_list_f32 (1024, 0) IN (3, 0)
                        time:   [2.1245 µs 2.1288 µs 2.1334 µs]
                        change: [−15.609% −15.371% −15.158%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  7 (7.00%) high mild
  2 (2.00%) high severe

in_list_i32 (1024, 0) IN (3, 0)
                        time:   [2.1255 µs 2.1299 µs 2.1343 µs]
                        change: [−15.936% −15.621% −15.328%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe

in_list_utf8(5) (1024, 0.2) IN (3, 0)
                        time:   [3.0553 µs 3.0637 µs 3.0724 µs]
                        change: [−15.090% −14.743% −14.427%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
  1 (1.00%) high severe

in_list_utf8(10) (1024, 0.2) IN (3, 0)
                        time:   [3.1270 µs 3.1356 µs 3.1447 µs]
                        change: [−11.801% −11.394% −11.027%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe

in_list_utf8(20) (1024, 0.2) IN (3, 0)
                        time:   [3.1550 µs 3.1655 µs 3.1788 µs]
                        change: [−13.039% −12.625% −12.188%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high severe

in_list_f32 (1024, 0.2) IN (3, 0)
                        time:   [2.9158 µs 2.9245 µs 2.9333 µs]
                        change: [−3.8833% −3.2912% −2.7119%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild

in_list_i32 (1024, 0.2) IN (3, 0)
                        time:   [2.8837 µs 2.8928 µs 2.9035 µs]
                        change: [−5.0262% −4.6043% −4.1990%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  1 (1.00%) low severe
  7 (7.00%) high mild
  1 (1.00%) high severe

in_list_utf8(5) (1024, 0) IN (10, 0)
                        time:   [2.1303 µs 2.1489 µs 2.1734 µs]
                        change: [−24.301% −21.837% −17.687%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
  1 (1.00%) high mild
  9 (9.00%) high severe

in_list_utf8(10) (1024, 0) IN (10, 0)
                        time:   [2.1435 µs 2.1549 µs 2.1756 µs]
                        change: [−24.680% −24.110% −23.317%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
  3 (3.00%) high severe

in_list_utf8(20) (1024, 0) IN (10, 0)
                        time:   [2.1390 µs 2.1454 µs 2.1522 µs]
                        change: [−24.712% −24.486% −24.246%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
  7 (7.00%) high mild
  1 (1.00%) high severe

in_list_f32 (1024, 0) IN (10, 0)
                        time:   [2.0991 µs 2.1036 µs 2.1088 µs]
                        change: [−15.814% −15.569% −15.301%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 9 outliers among 100 measurements (9.00%)
  7 (7.00%) high mild
  2 (2.00%) high severe

in_list_i32 (1024, 0) IN (10, 0)
                        time:   [2.1197 µs 2.1248 µs 2.1301 µs]
                        change: [−15.648% −15.282% −14.783%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe

in_list_utf8(5) (1024, 0.2) IN (10, 0)
                        time:   [3.0688 µs 3.0835 µs 3.1023 µs]
                        change: [−15.752% −14.224% −13.106%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  3 (3.00%) high mild
  3 (3.00%) high severe

in_list_utf8(10) (1024, 0.2) IN (10, 0)
                        time:   [3.1224 µs 3.1369 µs 3.1606 µs]
                        change: [−10.942% −10.579% −10.179%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe

in_list_utf8(20) (1024, 0.2) IN (10, 0)
                        time:   [3.1560 µs 3.1706 µs 3.1961 µs]
                        change: [−13.605% −13.319% −12.929%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
  2 (2.00%) high severe

in_list_f32 (1024, 0.2) IN (10, 0)
                        time:   [2.9480 µs 2.9641 µs 2.9883 µs]
                        change: [−1.7084% −1.0839% −0.4270%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 8 outliers among 100 measurements (8.00%)
  1 (1.00%) low mild
  5 (5.00%) high mild
  2 (2.00%) high severe

in_list_i32 (1024, 0.2) IN (10, 0)
                        time:   [2.9054 µs 2.9144 µs 2.9238 µs]
                        change: [−4.3963% −4.0561% −3.7253%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild

in_list_utf8(5) (1024, 0) IN (100, 0)
                        time:   [2.1311 µs 2.1366 µs 2.1430 µs]
                        change: [−30.391% −27.461% −25.061%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
  3 (3.00%) high mild
  5 (5.00%) high severe

in_list_utf8(10) (1024, 0) IN (100, 0)
                        time:   [2.1301 µs 2.1408 µs 2.1555 µs]
                        change: [−24.344% −23.667% −22.620%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  2 (2.00%) high mild
  4 (4.00%) high severe

in_list_utf8(20) (1024, 0) IN (100, 0)
                        time:   [2.1389 µs 2.1443 µs 2.1494 µs]
                        change: [−24.752% −24.532% −24.303%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe

in_list_f32 (1024, 0) IN (100, 0)
                        time:   [2.1481 µs 2.1567 µs 2.1676 µs]
                        change: [−14.858% −14.459% −14.017%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high severe

in_list_i32 (1024, 0) IN (100, 0)
                        time:   [2.1556 µs 2.1655 µs 2.1786 µs]
                        change: [−14.130% −13.702% −13.179%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) high mild
  2 (2.00%) high severe

in_list_utf8(5) (1024, 0.2) IN (100, 0)
                        time:   [3.3774 µs 3.4003 µs 3.4289 µs]
                        change: [−2.1519% −1.4661% −0.6108%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
  4 (4.00%) high severe

in_list_utf8(10) (1024, 0.2) IN (100, 0)
                        time:   [3.1737 µs 3.1907 µs 3.2192 µs]
                        change: [−10.181% −9.7063% −9.1830%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
  4 (4.00%) high mild
  4 (4.00%) high severe

in_list_utf8(20) (1024, 0.2) IN (100, 0)
                        time:   [3.3150 µs 3.3270 µs 3.3424 µs]
                        change: [−7.7706% −6.3142% −5.1134%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
  3 (3.00%) high severe

in_list_f32 (1024, 0.2) IN (100, 0)
                        time:   [3.6284 µs 3.7404 µs 3.8540 µs]
                        change: [+12.781% +15.878% +18.866%] (p = 0.00 < 0.05)
                        Performance has regressed.

in_list_i32 (1024, 0.2) IN (100, 0)
                        time:   [3.7092 µs 3.7750 µs 3.8491 µs]
                        change: [+25.967% +28.595% +31.510%] (p = 0.00 < 0.05)
                        Performance has regressed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A PR to port this to arrow itself: apache/arrow-rs#8814

Comment on lines 44 to 130
/// Maximum size for the thread-local hash buffer before truncation (4MB = 524,288 u64 elements).
/// The goal of this is to avoid unbounded memory growth that would appear as a memory leak.
/// We allow temporary allocations beyond this size, but after use the buffer is truncated
/// to this size.
const MAX_BUFFER_SIZE: usize = 524_288;

thread_local! {
/// Thread-local buffer for hash computations to avoid repeated allocations.
/// The buffer is reused across calls and truncated if it exceeds MAX_BUFFER_SIZE.
/// Defaults to a capacity of 8192 u64 elements which is the default batch size.
/// This corresponds to 64KB of memory.
static HASH_BUFFER: RefCell<Vec<u64>> = RefCell::new(Vec::with_capacity(8192));
}

/// Creates hashes for the given arrays using a thread-local buffer, then calls the provided callback
/// with an immutable reference to the computed hashes.
///
/// This function manages a thread-local buffer to avoid repeated allocations. The buffer is automatically
/// truncated if it exceeds [`MAX_BUFFER_SIZE`] after use.
///
/// # Arguments
/// * `arrays` - The arrays to hash (must contain at least one array)
/// * `random_state` - The random state for hashing
/// * `callback` - A function that receives an immutable reference to the hash slice and returns a result
///
/// # Errors
/// Returns an error if:
/// - No arrays are provided
/// - The function is called reentrantly (i.e., the callback invokes `with_hashes` again on the same thread)
/// - The function is called during or after thread destruction
///
/// # Example
/// ```ignore
/// use datafusion_common::hash_utils::{with_hashes, RandomState};
/// use arrow::array::{Int32Array, ArrayRef};
/// use std::sync::Arc;
///
/// let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
/// let random_state = RandomState::new();
///
/// let result = with_hashes([&array], &random_state, |hashes| {
/// // Use the hashes here
/// Ok(hashes.len())
/// })?;
/// ```
pub fn with_hashes<I, T, F, R>(
arrays: I,
random_state: &RandomState,
callback: F,
) -> Result<R>
where
I: IntoIterator<Item = T>,
T: AsDynArray,
F: FnOnce(&[u64]) -> Result<R>,
{
// Peek at the first array to determine buffer size without fully collecting
let mut iter = arrays.into_iter().peekable();

// Get the required size from the first array
let required_size = match iter.peek() {
Some(arr) => arr.as_dyn_array().len(),
None => return _internal_err!("with_hashes requires at least one array"),
};

HASH_BUFFER.try_with(|cell| {
let mut buffer = cell.try_borrow_mut()
.map_err(|_| _internal_datafusion_err!("with_hashes cannot be called reentrantly on the same thread"))?;

// Ensure buffer has sufficient length, clearing old values
buffer.clear();
buffer.resize(required_size, 0);

// Create hashes in the buffer - this consumes the iterator
create_hashes(iter, random_state, &mut buffer[..required_size])?;

// Execute the callback with an immutable slice
let result = callback(&buffer[..required_size])?;

// Cleanup: truncate if buffer grew too large
if buffer.capacity() > MAX_BUFFER_SIZE {
buffer.truncate(MAX_BUFFER_SIZE);
buffer.shrink_to_fit();
}

Ok(result)
}).map_err(|_| _internal_datafusion_err!("with_hashes cannot access thread-local storage during or after thread destruction"))?
}
Copy link
Contributor Author

@adriangb adriangb Nov 10, 2025

Choose a reason for hiding this comment

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

This can be its own PR if we really want to reduce the diff. Then we could also use it in all of the other call sites that have similar patterns. Otherwise I will do that as a follow up to avoid an extra sequencing step.

@alamb
Copy link
Contributor

alamb commented Nov 10, 2025

🤖 ./gh_compare_branch_bench.sh Benchmark Script Running
Linux aal-dev 6.14.0-1018-gcp #19~24.04.1-Ubuntu SMP Wed Sep 24 23:23:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing refactor-in-list (5958baf) to 1d8bc9b diff
BENCH_NAME=in_list
BENCH_COMMAND=cargo bench --bench in_list
BENCH_FILTER=
BENCH_BRANCH_NAME=refactor-in-list
Results will be posted here when complete

@alamb
Copy link
Contributor

alamb commented Nov 10, 2025

🤖: Benchmark completed

Details

group                                       main                                   refactor-in-list
-----                                       ----                                   ----------------
in_list_f32 (1024, 0) IN (1, 0)             1.00      4.2±0.01µs        ? ?/sec    1.38      5.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (10, 0)            1.00      4.3±0.07µs        ? ?/sec    1.39      5.9±0.31µs        ? ?/sec
in_list_f32 (1024, 0) IN (100, 0)           1.00      4.2±0.04µs        ? ?/sec    1.39      5.9±0.05µs        ? ?/sec
in_list_f32 (1024, 0) IN (3, 0)             1.00      4.3±0.04µs        ? ?/sec    1.38      5.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (1, 0)           1.00      5.5±0.02µs        ? ?/sec    1.72      9.5±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (10, 0)          1.00      5.5±0.02µs        ? ?/sec    1.72      9.5±0.04µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (100, 0)         1.00      5.6±0.01µs        ? ?/sec    1.59      8.9±0.07µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (3, 0)           1.00      5.5±0.02µs        ? ?/sec    1.72      9.5±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (1, 0)             1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (10, 0)            1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (100, 0)           1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (3, 0)             1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (1, 0)           1.00      5.6±0.02µs        ? ?/sec    1.64      9.3±0.03µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (10, 0)          1.00      5.6±0.03µs        ? ?/sec    1.59      8.9±0.05µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (100, 0)         1.00      5.5±0.03µs        ? ?/sec    1.66      9.1±0.03µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (3, 0)           1.00      5.6±0.02µs        ? ?/sec    1.64      9.2±0.02µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (1, 0)        1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (10, 0)       1.05      5.5±0.01µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (100, 0)      1.05      5.5±0.04µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (3, 0)        1.05      5.5±0.01µs        ? ?/sec    1.00      5.3±0.07µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (1, 0)      1.00      7.6±0.02µs        ? ?/sec    1.04      7.9±0.28µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (10, 0)     1.02      7.7±0.03µs        ? ?/sec    1.00      7.6±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (100, 0)    1.00      7.7±0.03µs        ? ?/sec    1.02      7.9±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (3, 0)      1.00      7.6±0.02µs        ? ?/sec    1.06      8.1±0.06µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (1, 0)        1.05      5.5±0.01µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (10, 0)       1.05      5.5±0.05µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (100, 0)      1.05      5.5±0.04µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (3, 0)        1.01      5.6±0.02µs        ? ?/sec    1.00      5.6±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (1, 0)      1.00      8.0±0.03µs        ? ?/sec    1.02      8.1±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (10, 0)     1.00      7.9±0.02µs        ? ?/sec    1.00      7.9±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (100, 0)    1.00      7.8±0.03µs        ? ?/sec    1.04      8.1±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (3, 0)      1.00      8.1±0.02µs        ? ?/sec    1.01      8.2±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (1, 0)         1.07      5.7±0.01µs        ? ?/sec    1.00      5.4±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (10, 0)        1.05      5.5±0.01µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (100, 0)       1.05      5.5±0.01µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (3, 0)         1.04      5.6±0.01µs        ? ?/sec    1.00      5.4±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (1, 0)       1.00      7.6±0.03µs        ? ?/sec    1.03      7.8±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (10, 0)      1.00      7.7±0.09µs        ? ?/sec    1.00      7.7±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (100, 0)     1.00      7.6±0.10µs        ? ?/sec    1.02      7.7±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (3, 0)       1.00      7.7±0.07µs        ? ?/sec    1.03      7.9±0.02µs        ? ?/sec

@alamb
Copy link
Contributor

alamb commented Nov 10, 2025

🤖 ./gh_compare_branch.sh Benchmark Script Running
Linux aal-dev 6.14.0-1018-gcp #19~24.04.1-Ubuntu SMP Wed Sep 24 23:23:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing refactor-in-list (5958baf) to 1d8bc9b diff using: tpch_mem clickbench_partitioned clickbench_extended
Results will be posted here when complete

@alamb
Copy link
Contributor

alamb commented Nov 10, 2025

🤖: Benchmark completed

Details

Comparing HEAD and refactor-in-list
--------------------
Benchmark clickbench_extended.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query        ┃        HEAD ┃ refactor-in-list ┃        Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 0     │  2777.82 ms │       2678.20 ms │     no change │
│ QQuery 1     │  1290.06 ms │       1211.18 ms │ +1.07x faster │
│ QQuery 2     │  2474.24 ms │       2379.79 ms │     no change │
│ QQuery 3     │  1154.78 ms │       1174.79 ms │     no change │
│ QQuery 4     │  2319.86 ms │       2357.21 ms │     no change │
│ QQuery 5     │ 28207.57 ms │      28664.09 ms │     no change │
│ QQuery 6     │  4217.49 ms │       4209.78 ms │     no change │
│ QQuery 7     │  3769.54 ms │       3819.30 ms │     no change │
└──────────────┴─────────────┴──────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary               ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)               │ 46211.36ms │
│ Total Time (refactor-in-list)   │ 46494.34ms │
│ Average Time (HEAD)             │  5776.42ms │
│ Average Time (refactor-in-list) │  5811.79ms │
│ Queries Faster                  │          1 │
│ Queries Slower                  │          0 │
│ Queries with No Change          │          7 │
│ Queries with Failure            │          0 │
└─────────────────────────────────┴────────────┘
--------------------
Benchmark clickbench_partitioned.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Query        ┃        HEAD ┃ refactor-in-list ┃       Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ QQuery 0     │     2.16 ms │          2.67 ms │ 1.24x slower │
│ QQuery 1     │    49.65 ms │         51.52 ms │    no change │
│ QQuery 2     │   135.59 ms │        134.87 ms │    no change │
│ QQuery 3     │   164.08 ms │        166.19 ms │    no change │
│ QQuery 4     │  1091.92 ms │       1185.57 ms │ 1.09x slower │
│ QQuery 5     │  1502.95 ms │       1529.07 ms │    no change │
│ QQuery 6     │     2.18 ms │          2.17 ms │    no change │
│ QQuery 7     │    55.01 ms │         55.25 ms │    no change │
│ QQuery 8     │  1457.37 ms │       1528.51 ms │    no change │
│ QQuery 9     │  1807.96 ms │       1929.02 ms │ 1.07x slower │
│ QQuery 10    │   369.53 ms │        371.14 ms │    no change │
│ QQuery 11    │   426.30 ms │        432.26 ms │    no change │
│ QQuery 12    │  1372.17 ms │       1431.30 ms │    no change │
│ QQuery 13    │  2109.41 ms │       2204.70 ms │    no change │
│ QQuery 14    │  1263.77 ms │       1331.42 ms │ 1.05x slower │
│ QQuery 15    │  1215.18 ms │       1288.39 ms │ 1.06x slower │
│ QQuery 16    │  2676.51 ms │       2785.60 ms │    no change │
│ QQuery 17    │  2656.57 ms │       2756.38 ms │    no change │
│ QQuery 18    │  5272.77 ms │       5056.54 ms │    no change │
│ QQuery 19    │   129.88 ms │        129.05 ms │    no change │
│ QQuery 20    │  2061.28 ms │       1963.31 ms │    no change │
│ QQuery 21    │  2354.89 ms │       2289.82 ms │    no change │
│ QQuery 22    │  3977.21 ms │       3932.08 ms │    no change │
│ QQuery 23    │ 13061.60 ms │      12740.18 ms │    no change │
│ QQuery 24    │   214.45 ms │        217.95 ms │    no change │
│ QQuery 25    │   480.33 ms │        467.01 ms │    no change │
│ QQuery 26    │   219.28 ms │        218.41 ms │    no change │
│ QQuery 27    │  2835.71 ms │       2790.31 ms │    no change │
│ QQuery 28    │ 23610.35 ms │      23726.78 ms │    no change │
│ QQuery 29    │   954.00 ms │        965.25 ms │    no change │
│ QQuery 30    │  1332.21 ms │       1378.11 ms │    no change │
│ QQuery 31    │  1398.71 ms │       1405.24 ms │    no change │
│ QQuery 32    │  5206.28 ms │       5062.77 ms │    no change │
│ QQuery 33    │  6113.75 ms │       5878.53 ms │    no change │
│ QQuery 34    │  6040.23 ms │       6239.14 ms │    no change │
│ QQuery 35    │  2039.85 ms │       2087.06 ms │    no change │
│ QQuery 36    │   123.50 ms │        123.60 ms │    no change │
│ QQuery 37    │    52.88 ms │         53.42 ms │    no change │
│ QQuery 38    │   122.19 ms │        119.02 ms │    no change │
│ QQuery 39    │   200.89 ms │        202.32 ms │    no change │
│ QQuery 40    │    43.46 ms │         42.54 ms │    no change │
│ QQuery 41    │    39.08 ms │         39.89 ms │    no change │
│ QQuery 42    │    32.19 ms │         32.73 ms │    no change │
└──────────────┴─────────────┴──────────────────┴──────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Benchmark Summary               ┃            ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Total Time (HEAD)               │ 96275.29ms │
│ Total Time (refactor-in-list)   │ 96347.11ms │
│ Average Time (HEAD)             │  2238.96ms │
│ Average Time (refactor-in-list) │  2240.63ms │
│ Queries Faster                  │          0 │
│ Queries Slower                  │          5 │
│ Queries with No Change          │         38 │
│ Queries with Failure            │          0 │
└─────────────────────────────────┴────────────┘
--------------------
Benchmark tpch_mem_sf1.json
--------------------
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Query        ┃      HEAD ┃ refactor-in-list ┃        Change ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ QQuery 1     │ 126.12 ms │        128.67 ms │     no change │
│ QQuery 2     │  28.13 ms │         28.44 ms │     no change │
│ QQuery 3     │  39.13 ms │         37.95 ms │     no change │
│ QQuery 4     │  29.20 ms │         28.51 ms │     no change │
│ QQuery 5     │  86.51 ms │         87.25 ms │     no change │
│ QQuery 6     │  19.40 ms │         19.56 ms │     no change │
│ QQuery 7     │ 222.29 ms │        234.26 ms │  1.05x slower │
│ QQuery 8     │  33.74 ms │         35.66 ms │  1.06x slower │
│ QQuery 9     │ 105.20 ms │        103.95 ms │     no change │
│ QQuery 10    │  63.32 ms │         64.69 ms │     no change │
│ QQuery 11    │  17.41 ms │         17.86 ms │     no change │
│ QQuery 12    │  50.75 ms │         50.54 ms │     no change │
│ QQuery 13    │  47.06 ms │         46.43 ms │     no change │
│ QQuery 14    │  14.12 ms │         13.68 ms │     no change │
│ QQuery 15    │  24.76 ms │         24.54 ms │     no change │
│ QQuery 16    │  24.94 ms │         25.48 ms │     no change │
│ QQuery 17    │ 151.99 ms │        150.96 ms │     no change │
│ QQuery 18    │ 279.88 ms │        278.09 ms │     no change │
│ QQuery 19    │  38.17 ms │         38.40 ms │     no change │
│ QQuery 20    │  49.49 ms │         50.16 ms │     no change │
│ QQuery 21    │ 326.27 ms │        330.86 ms │     no change │
│ QQuery 22    │  21.63 ms │         17.66 ms │ +1.22x faster │
└──────────────┴───────────┴──────────────────┴───────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Benchmark Summary               ┃           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Total Time (HEAD)               │ 1799.50ms │
│ Total Time (refactor-in-list)   │ 1813.62ms │
│ Average Time (HEAD)             │   81.80ms │
│ Average Time (refactor-in-list) │   82.44ms │
│ Queries Faster                  │         1 │
│ Queries Slower                  │         2 │
│ Queries with No Change          │        19 │
│ Queries with Failure            │         0 │
└─────────────────────────────────┴───────────┘

@adriangb
Copy link
Contributor Author

🤖: Benchmark completed

Details

@alamb I'm not sure what's going on with these benchmarks. @friendlymatthew and I both ran it independently and got mostly big speedups as per above. Could you maybe try on your local machine? Here's how I've been running them:

git checkout main && git log -1 --oneline && cargo bench --bench in_list -- --save-baseline before && git checkout refactor-in-list && git log -1 --oneline && cargo bench --bench in_list -- --baseline before

codetyri0n pushed a commit to codetyri0n/datafusion that referenced this pull request Nov 11, 2025
## Background

This PR is part of an EPIC to push down hash table references from
HashJoinExec into scans. The EPIC is tracked in
apache#17171.

A "target state" is tracked in
apache#18393.
There is a series of PRs to get us to this target state in smaller more
reviewable changes that are still valuable on their own:
- (This PR): apache#18448
- apache#18449 (depends on
apache#18448)
- apache#18451

## Changes in this PR

Change create_hashes and related functions to work with &dyn Array
references instead of requiring ArrayRef (Arc-wrapped arrays). This
avoids unnecessary Arc::clone() calls and enables calls that only have
an &dyn Array to use the hashing utilities.

- Add create_hashes_from_arrays(&[&dyn Array]) function
- Refactor hash_dictionary, hash_list_array, hash_fixed_list_array to
use references instead of cloning
- Extract hash_single_array() helper for common logic

---------

Co-authored-by: Andrew Lamb <[email protected]>
@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

🤖 ./gh_compare_branch_bench.sh Benchmark Script Running
Linux aal-dev 6.14.0-1018-gcp #19~24.04.1-Ubuntu SMP Wed Sep 24 23:23:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing refactor-in-list (5958baf) to 1d8bc9b diff
BENCH_NAME=in_list
BENCH_COMMAND=cargo bench --bench in_list
BENCH_FILTER=
BENCH_BRANCH_NAME=refactor-in-list
Results will be posted here when complete

@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

🤖: Benchmark completed

Details

group                                       main                                   refactor-in-list
-----                                       ----                                   ----------------
in_list_f32 (1024, 0) IN (1, 0)             1.00      4.3±0.03µs        ? ?/sec    1.37      5.9±0.02µs        ? ?/sec
in_list_f32 (1024, 0) IN (10, 0)            1.00      4.3±0.01µs        ? ?/sec    1.38      5.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (100, 0)           1.00      4.3±0.02µs        ? ?/sec    1.39      5.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (3, 0)             1.00      4.3±0.02µs        ? ?/sec    1.38      5.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (1, 0)           1.00      5.9±0.03µs        ? ?/sec    1.65      9.7±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (10, 0)          1.00      5.6±0.01µs        ? ?/sec    1.71      9.6±0.04µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (100, 0)         1.00      5.6±0.01µs        ? ?/sec    1.62      9.1±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (3, 0)           1.00      5.8±0.03µs        ? ?/sec    1.66      9.6±0.03µs        ? ?/sec
in_list_i32 (1024, 0) IN (1, 0)             1.00      4.2±0.02µs        ? ?/sec    1.39      5.9±0.10µs        ? ?/sec
in_list_i32 (1024, 0) IN (10, 0)            1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (100, 0)           1.00      4.2±0.01µs        ? ?/sec    1.41      5.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (3, 0)             1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (1, 0)           1.00      5.7±0.03µs        ? ?/sec    1.67      9.5±0.03µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (10, 0)          1.00      5.6±0.06µs        ? ?/sec    1.64      9.1±0.03µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (100, 0)         1.00      5.5±0.05µs        ? ?/sec    1.66      9.1±0.03µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (3, 0)           1.00      5.7±0.02µs        ? ?/sec    1.67      9.4±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (1, 0)        1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (10, 0)       1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (100, 0)      1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (3, 0)        1.09      5.7±0.02µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (1, 0)      1.00      7.6±0.03µs        ? ?/sec    1.05      7.9±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (10, 0)     1.00      7.7±0.02µs        ? ?/sec    1.00      7.8±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (100, 0)    1.02      7.7±0.04µs        ? ?/sec    1.00      7.6±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (3, 0)      1.00      7.8±0.02µs        ? ?/sec    1.06      8.2±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (1, 0)        1.05      5.5±0.05µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (10, 0)       1.04      5.5±0.02µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (100, 0)      1.05      5.5±0.06µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (3, 0)        1.05      5.7±0.01µs        ? ?/sec    1.00      5.5±0.08µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (1, 0)      1.01      8.1±0.03µs        ? ?/sec    1.00      8.1±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (10, 0)     1.02      8.1±0.03µs        ? ?/sec    1.00      7.9±0.05µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (100, 0)    1.01      8.0±0.04µs        ? ?/sec    1.00      7.9±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (3, 0)      1.01      8.0±0.03µs        ? ?/sec    1.00      8.0±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (1, 0)         1.06      5.7±0.05µs        ? ?/sec    1.00      5.4±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (10, 0)        1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (100, 0)       1.04      5.5±0.06µs        ? ?/sec    1.00      5.3±0.06µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (3, 0)         1.04      5.7±0.01µs        ? ?/sec    1.00      5.5±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (1, 0)       1.00      7.6±0.03µs        ? ?/sec    1.04      7.9±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (10, 0)      1.00      7.6±0.03µs        ? ?/sec    1.02      7.8±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (100, 0)     1.08      8.1±0.06µs        ? ?/sec    1.00      7.5±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (3, 0)       1.00      7.5±0.03µs        ? ?/sec    1.03      7.8±0.04µs        ? ?/sec

@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

I don't know what is going on on the benchmarks 🤔

I ran this on my laptop:

gh pr checkout https://github.com/apache/datafusion/pull/18449 && git log -1 --oneline && cargo bench --bench in_list -- --save-baseline in_list && git checkout `git merge-base apache/main head` && git log -1 --oneline && cargo bench --bench in_list -- --save-baseline before

Then critcmp reports:

group                                       before                                 in_list
-----                                       ------                                 -------
in_list_f32 (1024, 0) IN (1, 0)             1.18      2.7±0.06µs        ? ?/sec    1.00      2.3±0.02µs        ? ?/sec
in_list_f32 (1024, 0) IN (10, 0)            1.14      2.7±0.04µs        ? ?/sec    1.00      2.3±0.08µs        ? ?/sec
in_list_f32 (1024, 0) IN (100, 0)           1.16      2.7±0.04µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_f32 (1024, 0) IN (3, 0)             1.17      2.7±0.06µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (1, 0)           1.08      3.3±0.06µs        ? ?/sec    1.00      3.1±0.05µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (10, 0)          1.06      3.3±0.08µs        ? ?/sec    1.00      3.1±0.05µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (100, 0)         1.03      3.4±0.05µs        ? ?/sec    1.00      3.3±0.06µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (3, 0)           1.07      3.3±0.08µs        ? ?/sec    1.00      3.1±0.04µs        ? ?/sec
in_list_i32 (1024, 0) IN (1, 0)             1.15      2.6±0.02µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_i32 (1024, 0) IN (10, 0)            1.22      2.8±0.04µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_i32 (1024, 0) IN (100, 0)           1.16      2.7±0.04µs        ? ?/sec    1.00      2.3±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (3, 0)             1.25      2.9±0.06µs        ? ?/sec    1.00      2.3±0.02µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (1, 0)           1.16      3.6±0.10µs        ? ?/sec    1.00      3.1±0.06µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (10, 0)          1.09      3.4±0.05µs        ? ?/sec    1.00      3.2±0.03µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (100, 0)         1.03      3.2±0.05µs        ? ?/sec    1.00      3.1±0.12µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (3, 0)           1.18      3.6±0.14µs        ? ?/sec    1.00      3.1±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (1, 0)        1.71      3.9±0.05µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (10, 0)       1.74      4.0±0.08µs        ? ?/sec    1.00      2.3±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (100, 0)      1.75      4.0±0.05µs        ? ?/sec    1.00      2.3±0.02µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (3, 0)        1.75      4.0±0.07µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (1, 0)      1.26      4.1±0.04µs        ? ?/sec    1.00      3.3±0.05µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (10, 0)     1.25      4.2±0.07µs        ? ?/sec    1.00      3.4±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (100, 0)    1.21      4.2±0.08µs        ? ?/sec    1.00      3.5±0.06µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (3, 0)      1.29      4.3±0.11µs        ? ?/sec    1.00      3.3±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (1, 0)        1.74      4.0±0.08µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (10, 0)       1.73      4.0±0.10µs        ? ?/sec    1.00      2.3±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (100, 0)      1.74      4.0±0.12µs        ? ?/sec    1.00      2.3±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (3, 0)        1.75      4.0±0.08µs        ? ?/sec    1.00      2.3±0.05µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (1, 0)      1.22      4.2±0.06µs        ? ?/sec    1.00      3.4±0.05µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (10, 0)     1.23      4.2±0.06µs        ? ?/sec    1.00      3.4±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (100, 0)    1.17      4.2±0.05µs        ? ?/sec    1.00      3.6±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (3, 0)      1.23      4.2±0.12µs        ? ?/sec    1.00      3.4±0.06µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (1, 0)         1.68      3.9±0.05µs        ? ?/sec    1.00      2.3±0.05µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (10, 0)        1.78      4.1±0.08µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (100, 0)       1.76      4.0±0.07µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (3, 0)         1.76      4.0±0.07µs        ? ?/sec    1.00      2.3±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (1, 0)       1.27      4.2±0.06µs        ? ?/sec    1.00      3.3±0.04µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (10, 0)      1.26      4.2±0.08µs        ? ?/sec    1.00      3.4±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (100, 0)     1.25      4.3±0.06µs        ? ?/sec    1.00      3.4±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (3, 0)       1.34      4.4±0.29µs        ? ?/sec    1.00      3.3±0.05µs        ? ?/sec

@adriangb
Copy link
Contributor Author

So something is up with the automated benchmarks? If I'm reading your report correctly this branch is faster in practically all cases, including those that the automated benchmarks reported it is worse in. Am I missing something here?

@adriangb
Copy link
Contributor Author

Could it somehow be architecture related? I assume we both have ARM MacBooks while the automated runner is x86?

@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

So something is up with the automated benchmarks? If I'm reading your report correctly this branch is faster in practically all cases, including those that the automated benchmarks reported it is worse in. Am I missing something here?

That is my reading of the benchmarks too

Could it somehow be architecture related? I assume we both have ARM MacBooks while the automated runner is x86?

It is a good theory -- I am rerunning the same commands on my benchmarking machine (GCP c2-standard-8 (8 vCPUs, 32 GB Memory)) and will report results

@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

Here are the results from the gcp runner (aka I can reproduce my script and it shows several cases much slower)

alamb@aal-dev:~/arrow-datafusion10$ critcmp before in_list
group                                       before                                 in_list
-----                                       ------                                 -------
in_list_f32 (1024, 0) IN (1, 0)             1.00      4.2±0.02µs        ? ?/sec    1.38      5.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (10, 0)            1.00      4.3±0.05µs        ? ?/sec    1.38      5.9±0.04µs        ? ?/sec
in_list_f32 (1024, 0) IN (100, 0)           1.00      4.3±0.08µs        ? ?/sec    1.37      5.9±0.04µs        ? ?/sec
in_list_f32 (1024, 0) IN (3, 0)             1.00      4.3±0.02µs        ? ?/sec    1.39      5.9±0.05µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (1, 0)           1.00      5.5±0.02µs        ? ?/sec    1.61      8.9±0.04µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (10, 0)          1.00      5.4±0.02µs        ? ?/sec    1.71      9.3±0.06µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (100, 0)         1.00      5.5±0.03µs        ? ?/sec    1.58      8.8±0.07µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (3, 0)           1.00      5.5±0.02µs        ? ?/sec    1.64      8.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (1, 0)             1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (10, 0)            1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (100, 0)           1.00      4.2±0.01µs        ? ?/sec    1.40      5.9±0.02µs        ? ?/sec
in_list_i32 (1024, 0) IN (3, 0)             1.00      4.2±0.02µs        ? ?/sec    1.40      5.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (1, 0)           1.00      5.7±0.02µs        ? ?/sec    1.58      9.0±0.04µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (10, 0)          1.00      5.5±0.04µs        ? ?/sec    1.53      8.5±0.04µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (100, 0)         1.00      5.5±0.02µs        ? ?/sec    1.55      8.5±0.04µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (3, 0)           1.00      5.7±0.06µs        ? ?/sec    1.57      8.9±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (1, 0)        1.04      5.5±0.02µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (10, 0)       1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (100, 0)      1.05      5.5±0.01µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (3, 0)        1.05      5.5±0.06µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (1, 0)      1.00      7.6±0.02µs        ? ?/sec    1.06      8.0±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (10, 0)     1.00      7.7±0.02µs        ? ?/sec    1.03      7.9±0.06µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (100, 0)    1.00      7.9±0.03µs        ? ?/sec    1.00      7.9±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (3, 0)      1.00      7.6±0.02µs        ? ?/sec    1.05      7.9±0.08µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (1, 0)        1.05      5.5±0.02µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (10, 0)       1.05      5.5±0.05µs        ? ?/sec    1.00      5.3±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (100, 0)      1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (3, 0)        1.06      5.6±0.01µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (1, 0)      1.00      7.9±0.03µs        ? ?/sec    1.01      8.0±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (10, 0)     1.00      7.9±0.04µs        ? ?/sec    1.02      8.0±0.02µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (100, 0)    1.01      7.9±0.03µs        ? ?/sec    1.00      7.8±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (3, 0)      1.01      8.1±0.07µs        ? ?/sec    1.00      8.0±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (1, 0)         1.06      5.6±0.03µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (10, 0)        1.04      5.5±0.05µs        ? ?/sec    1.00      5.3±0.04µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (100, 0)       1.05      5.5±0.02µs        ? ?/sec    1.00      5.3±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (3, 0)         1.05      5.5±0.05µs        ? ?/sec    1.00      5.3±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (1, 0)       1.00      7.7±0.02µs        ? ?/sec    1.06      8.2±0.04µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (10, 0)      1.00      7.8±0.04µs        ? ?/sec    1.02      8.0±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (100, 0)     1.00      7.6±0.03µs        ? ?/sec    1.01      7.7±0.05µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (3, 0)       1.00      7.8±0.02µs        ? ?/sec    1.04      8.1±0.05µs        ? ?/sec

I am also going to see if RUSTFLAGS='-C target-cpu=native' makes any difference

@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

Results are the same when I used target-native

@alamb
Copy link
Contributor

alamb commented Nov 11, 2025

I could reproduce the slowdown on a local x86_64 dedicated machine (windows!) so i think it is reasonable to conclude this is something related to the platform

Screenshot 2025-11-11 at 2 41 24 PM

@adriangb
Copy link
Contributor Author

adriangb commented Nov 11, 2025 via email

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

Labels

common Related to common crate physical-expr Changes to the physical-expr crates physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants