11// SPDX-License-Identifier: Apache-2.0
22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
4+ use std:: ops:: Range ;
5+
46use num_traits:: Zero ;
57use vortex_buffer:: BitBufferMut ;
68use vortex_buffer:: Buffer ;
@@ -32,14 +34,54 @@ use crate::validity::Validity;
3234/// Note that this is somewhat arbitrarily chosen...
3335const MASK_EXPANSION_DENSITY_THRESHOLD : f64 = 0.05 ;
3436
35- /// Construct an element mask from contiguous list offsets and a selection mask.
37+ /// Crop when at least `1 / threshold` of referenced elements are unselected.
38+ const PERCENTAGE_REFERENCED_UNSELECTED_ELEMENTS_THRESHOLD : usize = 20 ;
39+
40+ /// Minimum percentage of referenced-but-unselected prefix and suffix elements required before
41+ /// cropping.
42+ const N_REFERENCED_UNSELECTED_ELEMENTS_THRESHOLD : usize = 1024 ;
43+
44+ /// Return the element range to construct new mask over and to subsequently filter. In the general case this will be the range
45+ /// of elements referenced by a sublist.
46+ ///
47+ /// If there are enough elements in head or tail that are referenced but not selected, it is more efficient
48+ /// to bound the element range to the first and last selected indices. We can then expand the
49+ /// mask only over this subset of elements, slice the elements array, and then filter. This avoids the
50+ /// overhead of potentially wasteful mask reconstruction.
51+ ///
52+ /// Returns the range and a flag indicating whether the range is a subinterval of the referenced element range.
53+ fn element_range_from_offsets < O : IntegerPType > (
54+ offsets : & [ O ] ,
55+ selection : & MaskValuesRef ,
56+ ) -> ( Range < usize > , bool ) {
57+ let referenced_elements_range = offsets[ 0 ] . as_ ( ) ..offsets[ offsets. len ( ) - 1 ] . as_ ( ) ;
58+
59+ let selected_indices = selection. indices ( ) ;
60+ let first_selected_sublist_index = selected_indices[ 0 ] ;
61+ let last_selected_sublist_index = selected_indices[ selected_indices. len ( ) - 1 ] ;
62+ let selected_elements_range =
63+ offsets[ first_selected_sublist_index] . as_ ( ) ..offsets[ last_selected_sublist_index + 1 ] . as_ ( ) ;
64+ let trimmed_element_count = referenced_elements_range. len ( ) - selected_elements_range. len ( ) ;
65+
66+ if trimmed_element_count >= N_REFERENCED_UNSELECTED_ELEMENTS_THRESHOLD
67+ && trimmed_element_count. saturating_mul ( PERCENTAGE_REFERENCED_UNSELECTED_ELEMENTS_THRESHOLD )
68+ >= referenced_elements_range. len ( )
69+ {
70+ ( selected_elements_range, true )
71+ } else {
72+ ( referenced_elements_range, false )
73+ }
74+ }
75+
76+ /// Construct an element mask relative to `element_range` from contiguous list offsets and an
77+ /// outer-row selection mask.
3678pub fn element_mask_from_offsets < O : IntegerPType > (
3779 offsets : & [ O ] ,
3880 selection : & MaskValuesRef ,
81+ element_range : & Range < usize > ,
3982) -> Mask {
40- let first_offset = offsets. first ( ) . map_or ( 0 , |first_offset| first_offset. as_ ( ) ) ;
41- let last_offset = offsets. last ( ) . map_or ( 0 , |last_offset| last_offset. as_ ( ) ) ;
42- let len = last_offset - first_offset;
83+ let first_offset = element_range. start ;
84+ let len = element_range. end - first_offset;
4385
4486 let mut mask_builder = BitBufferMut :: with_capacity ( len) ;
4587
@@ -119,7 +161,7 @@ impl FilterKernel for List {
119161 // TODO(ngates): for ultra-sparse masks, we don't need to optimize the entire offsets.
120162 let offsets = array. offsets ( ) . clone ( ) ;
121163
122- let ( new_offsets, element_mask) =
164+ let ( new_offsets, element_range , range_is_subinterval , element_mask) =
123165 match_each_integer_ptype ! ( offsets. dtype( ) . as_ptype( ) , |O | {
124166 let offsets_buffer = offsets. execute:: <Buffer <O >>( ctx) ?;
125167 let offsets = offsets_buffer. as_slice( ) ;
@@ -135,12 +177,27 @@ impl FilterKernel for List {
135177
136178 // TODO(ngates): for very dense masks, there may be no point in filtering the elements,
137179 // and instead we should construct a view against the unfiltered elements.
138- let element_mask = element_mask_from_offsets:: <O >( offsets, selection) ;
180+ let ( element_range, range_is_subinterval) =
181+ element_range_from_offsets:: <O >( offsets, selection) ;
182+ let element_mask =
183+ element_mask_from_offsets:: <O >( offsets, selection, & element_range) ;
139184
140- ( new_offsets. freeze( ) . into_array( ) , element_mask)
185+ (
186+ new_offsets. freeze( ) . into_array( ) ,
187+ element_range,
188+ range_is_subinterval,
189+ element_mask,
190+ )
141191 } ) ;
142192
143- let new_elements = array. sliced_elements ( ) ?. filter ( element_mask) ?;
193+ let new_elements = if range_is_subinterval {
194+ array
195+ . elements ( )
196+ . slice ( element_range) ?
197+ . filter ( element_mask) ?
198+ } else {
199+ array. sliced_elements ( ) ?. filter ( element_mask) ?
200+ } ;
144201
145202 // SAFETY: new_offsets are monotonically increasing starting from 0 with length
146203 // true_count + 1, and the elements have been filtered to match.
@@ -149,3 +206,123 @@ impl FilterKernel for List {
149206 } ) )
150207 }
151208}
209+
210+ #[ cfg( test) ]
211+ mod tests {
212+ use vortex_error:: VortexResult ;
213+ use vortex_error:: vortex_bail;
214+ use vortex_mask:: Mask ;
215+
216+ use super :: element_mask_from_offsets;
217+ use super :: element_range_from_offsets;
218+
219+ #[ test]
220+ fn element_mask_excludes_unselected_prefix_and_suffix ( ) -> VortexResult < ( ) > {
221+ let Mask :: Values ( selection) = Mask :: from_indices ( 5 , [ 2 ] ) else {
222+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
223+ } ;
224+
225+ let offsets = [ 10u32 , 20010 , 40010 , 60010 , 80010 , 100010 ] ;
226+ let ( range, range_is_subinterval) = element_range_from_offsets ( & offsets, & selection) ;
227+ let element_mask = element_mask_from_offsets ( & offsets, & selection, & range) ;
228+
229+ assert_eq ! ( range, 40010 ..60010 ) ;
230+ assert ! ( range_is_subinterval) ;
231+ assert ! ( element_mask. all_true( ) ) ;
232+ assert_eq ! ( element_mask. len( ) , 20000 ) ;
233+ Ok ( ( ) )
234+ }
235+
236+ #[ test]
237+ fn element_mask_retains_gaps_between_selected_lists ( ) -> VortexResult < ( ) > {
238+ let Mask :: Values ( selection) = Mask :: from_indices ( 5 , [ 1 , 3 ] ) else {
239+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
240+ } ;
241+
242+ let offsets = [ 10u32 , 20010 , 40010 , 60010 , 80010 , 100010 ] ;
243+ let ( range, range_is_subinterval) = element_range_from_offsets ( & offsets, & selection) ;
244+ let element_mask = element_mask_from_offsets ( & offsets, & selection, & range) ;
245+
246+ assert_eq ! ( range, 20010 ..80010 ) ;
247+ assert ! ( range_is_subinterval) ;
248+ assert_eq ! ( element_mask. len( ) , 60000 ) ;
249+ assert_eq ! ( element_mask. true_count( ) , 40000 ) ;
250+ Ok ( ( ) )
251+ }
252+
253+ #[ test]
254+ fn element_range_preserves_complete_range_for_short_lists ( ) -> VortexResult < ( ) > {
255+ let Mask :: Values ( selection) = Mask :: from_indices ( 5 , [ 2 ] ) else {
256+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
257+ } ;
258+
259+ let offsets = [ 10u32 , 20 , 30 , 40 , 50 , 60 ] ;
260+ let ( range, range_is_subinterval) = element_range_from_offsets ( & offsets, & selection) ;
261+ let element_mask = element_mask_from_offsets ( & offsets, & selection, & range) ;
262+
263+ assert_eq ! ( range, 10 ..60 ) ;
264+ assert ! ( !range_is_subinterval) ;
265+ assert_eq ! ( element_mask. len( ) , 50 ) ;
266+ assert_eq ! ( element_mask. true_count( ) , 10 ) ;
267+ Ok ( ( ) )
268+ }
269+
270+ #[ test]
271+ fn element_range_requires_minimum_savings ( ) -> VortexResult < ( ) > {
272+ let Mask :: Values ( selection) = Mask :: from_indices ( 2 , [ 0 ] ) else {
273+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
274+ } ;
275+
276+ let offsets = [ 0u32 , 512 , 1024 ] ;
277+ assert_eq ! (
278+ element_range_from_offsets( & offsets, & selection) ,
279+ ( 0 ..1024 , false )
280+ ) ;
281+ Ok ( ( ) )
282+ }
283+
284+ #[ test]
285+ fn element_range_requires_sufficient_savings_ratio ( ) -> VortexResult < ( ) > {
286+ let Mask :: Values ( selection) = Mask :: from_slices ( 100 , vec ! [ ( 0 , 96 ) ] ) else {
287+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
288+ } ;
289+ let offsets = ( 0 ..=100 ) . map ( |index| index * 1000 ) . collect :: < Vec < u32 > > ( ) ;
290+
291+ assert_eq ! (
292+ element_range_from_offsets( & offsets, & selection) ,
293+ ( 0 ..100_000 , false )
294+ ) ;
295+ Ok ( ( ) )
296+ }
297+
298+ #[ test]
299+ fn element_range_crops_at_sufficient_savings_ratio ( ) -> VortexResult < ( ) > {
300+ let Mask :: Values ( selection) = Mask :: from_slices ( 100 , vec ! [ ( 0 , 95 ) ] ) else {
301+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
302+ } ;
303+ let offsets = ( 0 ..=100 ) . map ( |index| index * 1000 ) . collect :: < Vec < u32 > > ( ) ;
304+
305+ assert_eq ! (
306+ element_range_from_offsets( & offsets, & selection) ,
307+ ( 0 ..95_000 , true )
308+ ) ;
309+ Ok ( ( ) )
310+ }
311+
312+ #[ test]
313+ fn element_mask_preserves_complete_range_for_edge_spanning_selection ( ) -> VortexResult < ( ) > {
314+ let Mask :: Values ( selection) = Mask :: from_indices ( 5 , [ 0 , 4 ] ) else {
315+ vortex_bail ! ( "a partially selective mask uses Mask::Values" )
316+ } ;
317+
318+ let offsets = [ 10u32 , 20010 , 40010 , 60010 , 80010 , 100010 ] ;
319+ let ( range, range_is_subinterval) = element_range_from_offsets ( & offsets, & selection) ;
320+ let element_mask = element_mask_from_offsets ( & offsets, & selection, & range) ;
321+
322+ assert_eq ! ( range, 10 ..100010 ) ;
323+ assert ! ( !range_is_subinterval) ;
324+ assert_eq ! ( element_mask. len( ) , 100000 ) ;
325+ assert_eq ! ( element_mask. true_count( ) , 40000 ) ;
326+ Ok ( ( ) )
327+ }
328+ }
0 commit comments