Conversation
|
run benchmark in_list |
|
🤖 |
|
🤖: Benchmark completed Details
|
datafusion/physical-expr/src/expressions/in_list/array_filter.rs
Outdated
Show resolved
Hide resolved
datafusion/physical-expr/src/expressions/in_list/array_filter.rs
Outdated
Show resolved
Hide resolved
|
run benchmarks |
|
🤖 |
|
run benchmark tpch tpchds |
|
🤖 Hi @Dandandan, thanks for the request (#19390 (comment)).
Please choose one or more of these with |
|
🤖: Benchmark completed Details
|
|
run benchmark tpch tpcds |
|
🤖 |
|
🤖: Benchmark completed Details
|
|
🤖 |
|
🤖: Benchmark completed Details
|
@Dandandan how do I think once this optim is done, there could be a lot to reuse for broadcast joins... |
For plain (non dynamic) filters, I think based on a treshold (<= 3) it either gets planned as a chain of or expressions or using |
7ba1c85 to
276a37f
Compare
|
run benchmark in_list |
276a37f to
d18b346
Compare
|
🤖 |
|
🤖: Benchmark completed Details
|
2fc00e5 to
3db393a
Compare
|
run benchmark in_list |
|
🤖 |
|
🤖: Benchmark completed Details
|
|
run benchmark in_list |
|
🤖 |
|
🤖: Benchmark completed Details
|
8557785 to
565d6b8
Compare
0e458ea to
5cb8847
Compare
|
run benchmark in_list |
1 similar comment
|
run benchmark in_list |
My benchmark runner got killed for some reason (VM probably restarted). I started it again |
|
🤖 |
|
🤖: Benchmark completed Details
|
|
(those are quite some benchmark results) |
Rewrite in_list.rs benchmarks to provide targeted coverage of each InList optimization strategy with controlled match rates, null rates, and list sizes at strategy threshold boundaries. Benchmark coverage: - BitmapFilter: UInt8 (stack), Int16 (heap) - BranchlessFilter: Int32/Int64 at threshold boundaries - DirectProbeFilter: Int32/Int64 with large lists - Reinterpreted types: Float32, TimestampNanosecond - Utf8TwoStageFilter: short/long/mixed strings, prefix collisions - ByteViewMaskedFilter: inline/boundary/out-of-line strings - Dictionary arrays: Int32 and String with varying cardinalities - Null handling: shortcircuit paths, high null rates, NOT IN + nulls
Introduces the StaticFilter trait to decouple membership testing from InListExpr. Migrates existing HashSet optimizations into primitive_filter.rs to maintain performance parity while enabling future specialized implementations. Triggers for all constant IN lists.
Replaces HashSet<u8> with a 32-byte stack-allocated bitmap. Provides O(1) membership testing via bit-shifting, significantly reducing memory overhead and improving cache locality. Triggers for UInt8 arrays.
Implements an 8 KB heap-allocated bitmap for UInt16. Maintains O(1) performance while handling the larger value space. Triggers for UInt16 arrays.
Introduces zero-copy buffer reinterpretation to allow signed integers and other 1 or 2-byte primitive types (e.g. Float16) to use the high-performance bitmap filters. Triggers for all types with 1-byte or 2-byte width.
Adds a const-generic unrolled comparison chain that avoids CPU branching. Outperforms hash lookups for very small lists. Triggers for primitives when list size <= 32 (4-byte), 16 (8-byte), or 4 (16-byte).
Implements a fast hash table using open addressing with linear probing and a 25% load factor. Replaces the legacy HashSet for primitives, reducing indirection. Triggers for primitives when list size exceeds branchless thresholds.
Introduces a two-stage filter for ByteView types. Stage 1 uses a fast DirectProbeFilter on masked views (len + prefix) for quick rejection; Stage 2 performs full verification only for potential long-string matches. Triggers for Utf8View and BinaryView.
5cb8847 to
d5586a7
Compare
Yup :) I think PR is mostly ready now! I'll try and relaunch discussions around it next week. It's already nicely split up into commits, so should be "easy" to split up further into iterative PRs. Some changes are obviously huge wins for little complexity, others are more debatable. |
d5586a7 to
bc38ff6
Compare
Port of the two-stage View optimization to standard Utf8 and LargeUtf8 types. Encodes strings as i128 (len + prefix) for fast O(1) pre-filtering before falling back to full string comparison. Triggers for Utf8 and LargeUtf8.
bc38ff6 to
2e3da3a
Compare
|
run benchmark in_list |
|
🤖 |
|
🤖: Benchmark completed Details
|
Which issue does this PR close?
Rationale for this change
The current
InListexpression implementation uses a genericArrayStaticFilterthat relies onmake_comparatorfor all types, which adds significant overhead for primitive types. This PR introduces type-specialized filters that exploit the properties of different data types to achieve substantial performance improvements.What changes are included in this PR?
This PR refactors the
InListexpression to use specialized filter strategies based on data type and list size. The implementation is split into clean, incremental commits:Commit 1: Replace InList Benchmarks with Strategy-Focused Coverage
Rewrites
benches/in_list.rswith targeted benchmarks covering each optimization strategy (Bitmap, Branchless, DirectProbe, ByteView, Utf8TwoStage, Dictionary, null handling) at controlled match rates, null rates, and list sizes at strategy threshold boundaries.Commit 2: Modular StaticFilter Architecture
Refactors
InListExprfrom a single monolithic file into a module (in_list/) with submodules:static_filter.rs(trait),primitive_filter.rs(primitive optimizations),nested_filter.rs(complex type fallback),result.rs(result construction),strategy.rs(filter selection), andtransform.rs(type transformations). Introduces theStaticFiltertrait to decouple membership testing fromInListExpr, enabling pluggable filter implementations without changing the public API.Commit 3: Bitmap Filter for UInt8 (stack-based)
Commit 4: Bitmap Filter for UInt16 (heap-based)
Commit 5: Zero-Copy Reinterpretation for Int8/Int16/Float16
Commit 6: Branchless Filter for Small Primitive Lists
values.iter().fold(false, |acc, &v| acc | (v == needle))Commit 7: Direct Probe Hash Filter for Large Primitive Lists
std::HashSetfor primitives when list exceeds branchless thresholdsCommit 8: ByteView Two-Stage Filter (Utf8View/BinaryView)
Commit 9: Utf8/LargeUtf8 Two-Stage Filter
[len:u32][data:12 bytes]for quick rejectionPerformance Summary
Filter Selection Strategy
Are these changes tested?
Yes, all existing tests pass (37 tests in
in_listmodule). The optimizations are covered by the existing comprehensive test suite which includes:A comprehensive benchmark suite is included (
benches/in_list.rs) covering all optimization paths.Are there any user-facing changes?
No user-facing API changes. This is a pure performance optimization that maintains identical behavior.