Skip to content

Commit 9925e82

Browse files
Implement Zero-Copy Reinterpretation and enable Int8/Int16 Bitmaps
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.
1 parent 81ec379 commit 9925e82

4 files changed

Lines changed: 178 additions & 12 deletions

File tree

datafusion/physical-expr/src/expressions/in_list.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod primitive_filter;
4141
mod result;
4242
mod static_filter;
4343
mod strategy;
44+
mod transform;
4445

4546
use static_filter::StaticFilter;
4647
use strategy::instantiate_static_filter;

datafusion/physical-expr/src/expressions/in_list/primitive_filter.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,22 @@ impl<C: BitmapFilterConfig> BitmapFilter<C> {
131131
fn check(&self, needle: C::Native) -> bool {
132132
self.bits.get_bit(C::to_index(needle))
133133
}
134+
135+
/// Check membership using a raw values slice (zero-copy path for type reinterpretation).
136+
#[inline]
137+
pub(super) fn contains_slice(
138+
&self,
139+
values: &[C::Native],
140+
nulls: Option<&NullBuffer>,
141+
negated: bool,
142+
) -> BooleanArray {
143+
build_in_list_result(values.len(), nulls, self.null_count > 0, negated, |i| {
144+
// SAFETY: `build_in_list_result` invokes this closure for
145+
// indices in `0..values.len()`.
146+
let needle = unsafe { *values.get_unchecked(i) };
147+
self.check(needle)
148+
})
149+
}
134150
}
135151

136152
impl<C: BitmapFilterConfig> StaticFilter for BitmapFilter<C> {
@@ -345,9 +361,6 @@ macro_rules! primitive_static_filter {
345361
};
346362
}
347363

348-
// Generate specialized filters for all integer primitive types
349-
primitive_static_filter!(Int8StaticFilter, Int8Type);
350-
primitive_static_filter!(Int16StaticFilter, Int16Type);
351364
primitive_static_filter!(Int32StaticFilter, Int32Type);
352365
primitive_static_filter!(Int64StaticFilter, Int64Type);
353366
primitive_static_filter!(UInt32StaticFilter, UInt32Type);

datafusion/physical-expr/src/expressions/in_list/strategy.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use datafusion_common::Result;
2525
use super::array_static_filter::ArrayStaticFilter;
2626
use super::primitive_filter::*;
2727
use super::static_filter::StaticFilter;
28+
use super::transform::make_bitmap_filter;
2829

2930
pub(super) fn instantiate_static_filter(
3031
in_array: ArrayRef,
@@ -37,17 +38,14 @@ pub(super) fn instantiate_static_filter(
3738
_ => in_array,
3839
};
3940
match in_array.data_type() {
40-
// Integer primitive types
41-
DataType::Int8 => Ok(Arc::new(Int8StaticFilter::try_new(&in_array)?)),
42-
DataType::Int16 => Ok(Arc::new(Int16StaticFilter::try_new(&in_array)?)),
41+
DataType::Int8 | DataType::UInt8 => {
42+
make_bitmap_filter::<UInt8BitmapConfig>(&in_array)
43+
}
44+
DataType::Int16 | DataType::UInt16 => {
45+
make_bitmap_filter::<UInt16BitmapConfig>(&in_array)
46+
}
4347
DataType::Int32 => Ok(Arc::new(Int32StaticFilter::try_new(&in_array)?)),
4448
DataType::Int64 => Ok(Arc::new(Int64StaticFilter::try_new(&in_array)?)),
45-
DataType::UInt8 => Ok(Arc::new(BitmapFilter::<UInt8BitmapConfig>::try_new(
46-
&in_array,
47-
)?)),
48-
DataType::UInt16 => Ok(Arc::new(BitmapFilter::<UInt16BitmapConfig>::try_new(
49-
&in_array,
50-
)?)),
5149
DataType::UInt32 => Ok(Arc::new(UInt32StaticFilter::try_new(&in_array)?)),
5250
DataType::UInt64 => Ok(Arc::new(UInt64StaticFilter::try_new(&in_array)?)),
5351
// Float primitive types (use ordered wrappers for Hash/Eq)
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
//! Type transformation utilities for InList filters.
19+
//!
20+
//! Some filters only depend on fixed-width value bit patterns. For those cases,
21+
//! compatible primitive arrays can be reinterpreted to the filter's unsigned
22+
//! storage type without copying values.
23+
24+
use std::mem::size_of;
25+
use std::sync::Arc;
26+
27+
use arrow::array::{Array, ArrayRef, BooleanArray, PrimitiveArray};
28+
use arrow::buffer::ScalarBuffer;
29+
use arrow::datatypes::ArrowPrimitiveType;
30+
use datafusion_common::{Result, exec_datafusion_err};
31+
32+
use super::primitive_filter::{BitmapFilter, BitmapFilterConfig};
33+
use super::static_filter::{StaticFilter, handle_dictionary};
34+
35+
// =============================================================================
36+
// REINTERPRETING FILTERS (zero-copy type conversion)
37+
// =============================================================================
38+
39+
/// Reinterpreting filter for bitmap lookups (u8/u16).
40+
struct ReinterpretedBitmap<C: BitmapFilterConfig> {
41+
inner: BitmapFilter<C>,
42+
}
43+
44+
impl<C: BitmapFilterConfig> StaticFilter for ReinterpretedBitmap<C> {
45+
fn null_count(&self) -> usize {
46+
self.inner.null_count()
47+
}
48+
49+
fn contains(&self, v: &dyn Array, negated: bool) -> Result<BooleanArray> {
50+
handle_dictionary!(self, v, negated);
51+
52+
if v.data_type().primitive_width() != Some(size_of::<C::Native>()) {
53+
return Err(exec_datafusion_err!(
54+
"BitmapFilter: expected {}-byte primitive array, got {}",
55+
size_of::<C::Native>(),
56+
v.data_type()
57+
));
58+
}
59+
60+
let data = v.to_data();
61+
let values: &[C::Native] = &data.buffer::<C::Native>(0)[..v.len()];
62+
63+
Ok(self.inner.contains_slice(values, data.nulls(), negated))
64+
}
65+
}
66+
67+
/// Reinterprets a same-width primitive array as the target primitive type `T`.
68+
///
69+
/// This is a zero-copy operation: the returned array shares the original values
70+
/// buffer and null buffer. Callers must ensure the source array and target type
71+
/// have the same primitive width.
72+
#[inline]
73+
pub(crate) fn reinterpret_any_primitive_to<T: ArrowPrimitiveType>(
74+
array: &dyn Array,
75+
) -> ArrayRef {
76+
let data = array.to_data();
77+
let values = data.buffers()[0].clone();
78+
let buffer = ScalarBuffer::<T::Native>::new(values, data.offset(), data.len());
79+
Arc::new(PrimitiveArray::<T>::new(buffer, array.nulls().cloned()))
80+
}
81+
82+
/// Creates a bitmap filter for u8/u16 types, reinterpreting if needed.
83+
pub(crate) fn make_bitmap_filter<C>(
84+
in_array: &ArrayRef,
85+
) -> Result<Arc<dyn StaticFilter + Send + Sync>>
86+
where
87+
C: BitmapFilterConfig,
88+
{
89+
if in_array.data_type() == &C::ArrowType::DATA_TYPE {
90+
return Ok(Arc::new(BitmapFilter::<C>::try_new(in_array)?));
91+
}
92+
if in_array.data_type().primitive_width() != Some(size_of::<C::Native>()) {
93+
return Err(exec_datafusion_err!(
94+
"BitmapFilter: expected {}-byte primitive array for {} bitmap, got {}",
95+
size_of::<C::Native>(),
96+
C::DATA_TYPE_NAME,
97+
in_array.data_type()
98+
));
99+
}
100+
101+
let reinterpreted = reinterpret_any_primitive_to::<C::ArrowType>(in_array.as_ref());
102+
let inner = BitmapFilter::<C>::try_new(&reinterpreted)?;
103+
Ok(Arc::new(ReinterpretedBitmap { inner }))
104+
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::*;
109+
use std::sync::Arc;
110+
111+
use arrow::array::{ArrayRef, BooleanArray, Int8Array, Int16Array};
112+
113+
#[test]
114+
fn reinterpreted_bitmap_handles_signed_boundaries_and_slices() -> Result<()> {
115+
let haystack: ArrayRef = Arc::new(
116+
Int8Array::from(vec![Some(99), Some(i8::MIN), None, Some(-1), Some(42)])
117+
.slice(1, 3),
118+
);
119+
let filter = make_bitmap_filter::<
120+
super::super::primitive_filter::UInt8BitmapConfig,
121+
>(&haystack)?;
122+
let needles =
123+
Int8Array::from(vec![Some(7), Some(i8::MIN), Some(-1), None]).slice(1, 3);
124+
125+
assert_eq!(
126+
filter.contains(&needles, false)?,
127+
BooleanArray::from(vec![Some(true), Some(true), None])
128+
);
129+
130+
let haystack: ArrayRef = Arc::new(
131+
Int16Array::from(vec![
132+
Some(123),
133+
Some(i16::MIN),
134+
None,
135+
Some(-1),
136+
Some(i16::MAX),
137+
])
138+
.slice(1, 4),
139+
);
140+
let filter = make_bitmap_filter::<
141+
super::super::primitive_filter::UInt16BitmapConfig,
142+
>(&haystack)?;
143+
let needles =
144+
Int16Array::from(vec![Some(0), Some(i16::MIN), Some(7), Some(i16::MAX)])
145+
.slice(1, 3);
146+
147+
assert_eq!(
148+
filter.contains(&needles, false)?,
149+
BooleanArray::from(vec![Some(true), None, Some(true)])
150+
);
151+
152+
Ok(())
153+
}
154+
}

0 commit comments

Comments
 (0)