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

@alamb
Copy link
Contributor

alamb commented Nov 9, 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 (f412ead) 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

Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thanks @adriangb

I looked through the code and the basic idea makes a lot of sense to me 👍

I kicked off some benchmarks to see what impact, if any, this change has on performance. Assuming it is the same or better, I think it would be good to merge

I do suggest adding some slt level logic for struct IN lists if we don't already have some, but I don't think it is necessary

false => Some(negated),
}
})
let mut hashes_buf = vec![0u64; v.len()];
Copy link
Contributor

Choose a reason for hiding this comment

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

As a follow on PR we could potentially look into reusing this hashes_buf -- aka rather than reallocating each invocations of contains instead make a field (probably needs to be a Mutex or something) that is a Vec

})
let mut hashes_buf = vec![0u64; v.len()];
create_hashes([v], &self.state, &mut hashes_buf)?;
let cmp = make_comparator(v, in_array, SortOptions::default())?;
Copy link
Contributor

Choose a reason for hiding this comment

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

the comparator is some dynamic function -- the overhead of using the dynamic dispatch in the critical path may be substantial).

If it turns out to be too slow, we can potentially create specializations for comparisons (aka make a speicalized hash set for the different physical array types, and fall back to the dynamic comparator)

///
/// The `list` field will be empty when using this constructor, as the array is stored
/// directly in the static filter.
pub fn in_list_from_array(
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it would be more discoverable if this was a method on InList rather than a free function

Something like

impl InLIst
  fn new_from_array(  expr: Arc<dyn PhysicalExpr>,
    array: ArrayRef,
    negated: bool,
   ) -> Result<Self> {
...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I agree, I was just following the existing patterns

}

#[test]
fn in_list_struct() -> Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we also please add some .slt level tests for IN on a set?

@alamb
Copy link
Contributor

alamb commented Nov 9, 2025

🤖: Benchmark completed

Details

group                                       main                                   refactor-in-list
-----                                       ----                                   ----------------
in_list_f32 (1024, 0) IN (1, 0)             1.00      4.3±0.01µs        ? ?/sec    1.14      4.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (10, 0)            1.00      4.3±0.01µs        ? ?/sec    1.14      4.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (100, 0)           1.00      4.2±0.03µs        ? ?/sec    1.14      4.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (3, 0)             1.00      4.2±0.01µs        ? ?/sec    1.15      4.9±0.01µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (1, 0)           1.00      5.5±0.06µs        ? ?/sec    1.36      7.5±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (10, 0)          1.00      5.5±0.06µs        ? ?/sec    1.40      7.7±0.07µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (100, 0)         1.00      5.6±0.03µs        ? ?/sec    1.23      6.9±0.06µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (3, 0)           1.00      5.5±0.06µs        ? ?/sec    1.38      7.6±0.03µs        ? ?/sec
in_list_i32 (1024, 0) IN (1, 0)             1.00      4.2±0.01µs        ? ?/sec    1.16      4.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (10, 0)            1.00      4.2±0.01µs        ? ?/sec    1.16      4.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (100, 0)           1.00      4.2±0.01µs        ? ?/sec    1.16      4.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (3, 0)             1.00      4.2±0.01µs        ? ?/sec    1.16      4.9±0.01µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (1, 0)           1.00      5.7±0.02µs        ? ?/sec    1.31      7.5±0.04µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (10, 0)          1.00      5.6±0.01µs        ? ?/sec    1.29      7.2±0.04µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (100, 0)         1.00      5.5±0.03µs        ? ?/sec    1.22      6.7±0.04µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (3, 0)           1.00      5.7±0.04µs        ? ?/sec    1.31      7.5±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (1, 0)        1.09      5.5±0.02µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (10, 0)       1.09      5.5±0.01µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (100, 0)      1.05      5.5±0.05µs        ? ?/sec    1.00      5.3±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (3, 0)        1.13      5.7±0.06µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (1, 0)      1.00      7.6±0.04µs        ? ?/sec    1.05      8.0±0.07µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (10, 0)     1.00      7.8±0.04µs        ? ?/sec    1.01      7.8±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (100, 0)    1.03      8.0±0.09µs        ? ?/sec    1.00      7.8±0.07µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (3, 0)      1.00      7.7±0.04µs        ? ?/sec    1.06      8.1±0.06µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (1, 0)        1.10      5.6±0.19µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (10, 0)       1.09      5.5±0.01µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (100, 0)      1.09      5.5±0.01µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (3, 0)        1.04      5.5±0.01µs        ? ?/sec    1.00      5.3±0.01µ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.10µs        ? ?/sec    1.00      7.9±0.06µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (100, 0)    1.00      7.8±0.03µs        ? ?/sec    1.00      7.8±0.05µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (3, 0)      1.02      8.2±0.08µs        ? ?/sec    1.00      8.0±0.05µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (1, 0)         1.12      5.7±0.05µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (10, 0)        1.09      5.5±0.01µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (100, 0)       1.09      5.5±0.01µs        ? ?/sec    1.00      5.0±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (3, 0)         1.09      5.5±0.02µs        ? ?/sec    1.00      5.1±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (1, 0)       1.00      7.6±0.02µs        ? ?/sec    1.03      7.8±0.08µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (10, 0)      1.00      7.8±0.05µs        ? ?/sec    1.00      7.8±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (100, 0)     1.00      7.7±0.03µs        ? ?/sec    1.00      7.7±0.06µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (3, 0)       1.04      8.0±0.04µs        ? ?/sec    1.00      7.7±0.05µs        ? ?/sec

@adriangb
Copy link
Contributor Author

adriangb commented Nov 9, 2025

It looks like there are indeed some regressions. I propose we do two things:

  1. Add a create_hashes_unbuffered(…) -> &[u64] that uses a thread local to re-use the buffer. I think this will be helpful in other contexts as well.
  2. Create a make_typed_comparator that returns an enum that is typed for non-recursive types and delegates to a fallback dynamically typed variant for recursive types. I’ll implement it here for now but make a note that it would be good to upstream into arrow. When it is up streamed into arrow we can re-implement the current version in terms of their new version and deprecate the current function.

I think that will get us the broader type support and code re-use while avoiding any slowdown. Once we do the upstreaming into arrow it won’t even be any more code than it is now (a bit more code in arrow but not even that much). And we should be able to do it all in one PR here

@github-actions github-actions bot added the common Related to common crate label Nov 10, 2025
@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

@alamb
Copy link
Contributor

alamb commented Nov 13, 2025

@NGA-TRAN and @gene-bordegaray - this PR may interest you. Specifically, this is a step towards a more efficient pushdown of join filters -- #18451

The idea is to push down dynamic join filters as InLists -- perhaps you can help move this one along

@alamb
Copy link
Contributor

alamb commented Nov 14, 2025

@adriangb -- what is the status of this PR? is it ready for another look? Do you need help trying to get the performance back? I sort of lost track with everything else that is going on

@davidhewitt
Copy link
Contributor

I had a look at the perf regression on my local x86_64 dev box - I think the following might solve it pydantic#43 - appears to be singificantly better for me locally.

@alamb
Copy link
Contributor

alamb commented Nov 14, 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 (2db9927) to 0d52a1e 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 14, 2025

🤖: Benchmark completed

Details

group                                       main                                   refactor-in-list
-----                                       ----                                   ----------------
in_list_f32 (1024, 0) IN (1, 0)             1.17      5.0±0.02µs        ? ?/sec    1.00      4.3±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (10, 0)            1.16      5.0±0.03µs        ? ?/sec    1.00      4.3±0.01µs        ? ?/sec
in_list_f32 (1024, 0) IN (100, 0)           1.16      5.0±0.03µs        ? ?/sec    1.00      4.3±0.04µs        ? ?/sec
in_list_f32 (1024, 0) IN (3, 0)             1.16      5.0±0.03µs        ? ?/sec    1.00      4.3±0.01µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (1, 0)           1.05      5.7±0.01µs        ? ?/sec    1.00      5.4±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (10, 0)          1.06      5.8±0.02µs        ? ?/sec    1.00      5.5±0.02µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (100, 0)         1.00      5.7±0.02µs        ? ?/sec    1.01      5.7±0.03µs        ? ?/sec
in_list_f32 (1024, 0.2) IN (3, 0)           1.05      5.7±0.02µs        ? ?/sec    1.00      5.4±0.04µs        ? ?/sec
in_list_i32 (1024, 0) IN (1, 0)             1.00      4.2±0.01µs        ? ?/sec    1.02      4.3±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (10, 0)            1.00      4.2±0.01µs        ? ?/sec    1.02      4.3±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (100, 0)           1.00      4.2±0.01µs        ? ?/sec    1.02      4.3±0.01µs        ? ?/sec
in_list_i32 (1024, 0) IN (3, 0)             1.00      4.2±0.02µs        ? ?/sec    1.02      4.3±0.01µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (1, 0)           1.04      5.7±0.02µs        ? ?/sec    1.00      5.4±0.02µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (10, 0)          1.05      5.7±0.13µs        ? ?/sec    1.00      5.4±0.02µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (100, 0)         1.00      5.5±0.03µs        ? ?/sec    1.00      5.5±0.02µs        ? ?/sec
in_list_i32 (1024, 0.2) IN (3, 0)           1.05      5.7±0.02µs        ? ?/sec    1.00      5.4±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (1, 0)        1.26      5.5±0.01µs        ? ?/sec    1.00      4.4±0.02µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (10, 0)       1.26      5.5±0.01µs        ? ?/sec    1.00      4.4±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (100, 0)      1.26      5.5±0.01µs        ? ?/sec    1.00      4.4±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0) IN (3, 0)        1.26      5.5±0.06µs        ? ?/sec    1.00      4.4±0.01µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (1, 0)      1.29      7.7±0.10µs        ? ?/sec    1.00      5.9±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (10, 0)     1.29      7.8±0.03µs        ? ?/sec    1.00      6.0±0.03µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (100, 0)    1.29      8.1±0.05µs        ? ?/sec    1.00      6.3±0.04µs        ? ?/sec
in_list_utf8(10) (1024, 0.2) IN (3, 0)      1.27      7.7±0.03µs        ? ?/sec    1.00      6.0±0.05µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (1, 0)        1.26      5.5±0.12µs        ? ?/sec    1.00      4.4±0.06µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (10, 0)       1.26      5.5±0.05µs        ? ?/sec    1.00      4.4±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (100, 0)      1.26      5.5±0.02µs        ? ?/sec    1.00      4.4±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0) IN (3, 0)        1.23      5.5±0.01µs        ? ?/sec    1.00      4.5±0.01µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (1, 0)      1.30      7.9±0.03µs        ? ?/sec    1.00      6.1±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (10, 0)     1.27      7.9±0.03µs        ? ?/sec    1.00      6.2±0.03µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (100, 0)    1.28      7.9±0.02µs        ? ?/sec    1.00      6.2±0.04µs        ? ?/sec
in_list_utf8(20) (1024, 0.2) IN (3, 0)      1.35      8.2±0.04µs        ? ?/sec    1.00      6.1±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (1, 0)         1.28      5.7±0.02µs        ? ?/sec    1.00      4.5±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (10, 0)        1.26      5.5±0.01µs        ? ?/sec    1.00      4.4±0.02µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (100, 0)       1.26      5.5±0.01µs        ? ?/sec    1.00      4.4±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0) IN (3, 0)         1.26      5.5±0.05µs        ? ?/sec    1.00      4.4±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (1, 0)       1.32      7.8±0.02µs        ? ?/sec    1.00      5.9±0.01µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (10, 0)      1.28      7.7±0.07µs        ? ?/sec    1.00      6.0±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (100, 0)     1.29      7.8±0.03µs        ? ?/sec    1.00      6.1±0.03µs        ? ?/sec
in_list_utf8(5) (1024, 0.2) IN (3, 0)       1.28      7.7±0.02µs        ? ?/sec    1.00      6.0±0.05µs        ? ?/sec

@adriangb
Copy link
Contributor Author

Yay we got the performance back, and then some!

@alamb I need to resolve a ton of conflicts now. And I’d like to take the opportunity to benchmark with/without:

  • the enum for make_comparator. I’m leaning toward removing it if it doesn’t make a big difference and focusing on the arrow PR where we can more directly prove any performance improvement.
  • the thread local for the hashes buffer, I want to see if it actually helps or hurts

@davidhewitt
Copy link
Contributor

One possibility is that you replace the hashes buffer with a "hasher" similar to the comparator and then hash the value inside the iteration in StaticFilter::contains after the null check. (More similar to the original implementation which just called .hash_one).

@adriangb
Copy link
Contributor Author

One possibility is that you replace the hashes buffer with a "hasher" similar to the comparator and then hash the value inside the iteration in StaticFilter::contains after the null check. (More similar to the original implementation which just called .hash_one).

Yeah I think that makes a ton of sense here & in other places as well, but it would be a new thing to add into hash_utils.rs and I think more code than we have here. I also think there are going to be 3 patterns in the end:

  1. You want to hash the whole thing and have a buffer
  2. You want to hash the whole thing and don't have a buffer, re-using a thread local is nice
  3. You want to hash one at a time (i.e. using a hasher like you suggest)

I'd also guess if there are few nulls (2) will beat (3) but if there are a lot of nulls (3) will beat 2...

So for now I'm going to stick with the current approach if perf is acceptable, this is already a tangent on a larger change, we can make a ticket to consider doing (3) in the future.

@adriangb
Copy link
Contributor Author

I removed the enum comparator, benchmarks showed it was slower than the dynamic dispatch version. The thread local hashing / buffer re-use seems to be a big win though.

@alamb could you kick off benchmarks again? If they look good are we good to merge this?

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.

3 participants