This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new e51971ef perf(arrow): evaluate integer IN with a bitmap or binary 
search (#1058)
e51971ef is described below

commit e51971ef9f3dc88b284c29f86de61a9076be348f
Author: Dapeng Sun(孙大鹏) <[email protected]>
AuthorDate: Wed Oct 7 19:38:32 2026 +0800

    perf(arrow): evaluate integer IN with a bitmap or binary search (#1058)
---
 crates/paimon/src/arrow/residual.rs | 98 +++++++++++++++++++++++++++++++++----
 1 file changed, 89 insertions(+), 9 deletions(-)

diff --git a/crates/paimon/src/arrow/residual.rs 
b/crates/paimon/src/arrow/residual.rs
index eaf463a8..0cfa862d 100644
--- a/crates/paimon/src/arrow/residual.rs
+++ b/crates/paimon/src/arrow/residual.rs
@@ -1005,16 +1005,42 @@ fn set_membership_hash_mask(
         let array = array
             .as_any()
             .downcast_ref::<arrow_array::PrimitiveArray<T>>()?;
-        let set = literals
+        let mut set = literals
             .iter()
-            .map(|literal| integer_literal(literal).and_then(|v| 
T::Native::try_from(v).ok()))
-            .collect::<Option<HashSet<_>>>()?;
-        Some(
-            array
-                .iter()
-                .map(|value| Some(value.is_some_and(|v| set.contains(&v) == 
keep)))
-                .collect(),
-        )
+            .map(|literal| {
+                integer_literal(literal)
+                    .and_then(|v| T::Native::try_from(v).ok())
+                    .and_then(arrow_buffer::ArrowNativeType::to_i64)
+            })
+            .collect::<Option<Vec<i64>>>()?;
+        set.sort_unstable();
+        set.dedup();
+        let (min, max) = (*set.first()?, *set.last()?);
+        let values = array.values();
+        // Small literal spans use a bitmap; otherwise binary search. Both 
avoid per-call hashing.
+        let span = max.checked_sub(min).and_then(|d| usize::try_from(d).ok());
+        let member: Box<dyn Fn(i64) -> bool> = match span {
+            Some(span) if span < (1 << 16) => {
+                let mut bits = vec![0u64; span / 64 + 1];
+                for v in &set {
+                    let i = (v - min) as usize;
+                    bits[i / 64] |= 1 << (i % 64);
+                }
+                Box::new(move |v: i64| {
+                    v >= min && v <= max && {
+                        let i = (v - min) as usize;
+                        bits[i / 64] & (1 << (i % 64)) != 0
+                    }
+                })
+            }
+            _ => Box::new(move |v: i64| set.binary_search(&v).is_ok()),
+        };
+        let mask = arrow_buffer::BooleanBuffer::collect_bool(array.len(), 
|row| {
+            array.is_valid(row)
+                && arrow_buffer::ArrowNativeType::to_i64(values[row])
+                    .is_some_and(|v| member(v) == keep)
+        });
+        Some(BooleanArray::new(mask, None))
     }
 
     match array.data_type() {
@@ -1773,6 +1799,60 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_in_integer_bitmap_and_search_paths() {
+        use crate::spec::BigIntType;
+        let array: ArrayRef = Arc::new(arrow_array::Int64Array::from(vec![
+            Some(-5i64),
+            None,
+            Some(0),
+            Some(83),
+            Some(84),
+            Some(1 << 40),
+        ]));
+        let data_type = DataType::BigInt(BigIntType::new());
+        let near = vec![
+            Datum::Long(0),
+            Datum::Long(83),
+            Datum::Long(-5),
+            Datum::Long(83),
+        ];
+        let far = vec![Datum::Long(-5), Datum::Long(1 << 40)];
+        for (literals, expected) in [
+            (&near, vec![true, false, true, true, false, false]),
+            (&far, vec![true, false, false, false, false, true]),
+        ] {
+            let mask = evaluate_set_membership_predicate(
+                &array,
+                &data_type,
+                PredicateOperator::In,
+                literals,
+            )
+            .unwrap();
+            assert_eq!(mask.null_count(), 0);
+            assert_eq!(
+                mask.iter().map(Option::unwrap).collect::<Vec<_>>(),
+                expected
+            );
+            let mask = evaluate_set_membership_predicate(
+                &array,
+                &data_type,
+                PredicateOperator::NotIn,
+                literals,
+            )
+            .unwrap();
+            let not_expected = expected
+                .iter()
+                .enumerate()
+                .map(|(row, hit)| row != 1 && !hit)
+                .collect::<Vec<_>>();
+            assert_eq!(
+                mask.iter().map(Option::unwrap).collect::<Vec<_>>(),
+                not_expected
+            );
+        }
+    }
+
     #[test]
     fn test_blob_literal_uses_large_binary_scalar() {
         let scalar = literal_scalar_for_arrow_filter(

Reply via email to