11// SPDX-License-Identifier: Apache-2.0
22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
4- //! Measures the linear-search and binary-search crossover for constant-list membership .
4+ //! Compares compressed list membership with the canonical fallback .
55//!
6- //! The strategy benchmarks isolate lookup cost across mostly-missing and mixed probes. The kernel
7- //! benchmark includes scalar extraction, sorting, FastLanes decoding, and result construction.
8- //! The isolated lookup benchmark excludes sorting, so it favors binary search. Use the forced
9- //! full-kernel results to select the production strategy.
6+ //! The specialized session evaluates membership while it decodes FastLanes lanes. The fallback
7+ //! session decodes the complete array before the generic membership operation.
8+ //! Density cases stress the 4 KiB lookup-table boundary. Sparse cases exceed that boundary.
109//!
1110//! Run with `cargo bench -p vortex-fastlanes --bench bitpacking_list_contains`.
1211
@@ -22,100 +21,57 @@ use vortex_array::ArrayRef;
2221use vortex_array:: IntoArray ;
2322use vortex_array:: VortexSessionExecute ;
2423use vortex_array:: array_session;
25- use vortex_array:: arrays:: ConstantArray ;
24+ use vortex_array:: arrays:: BoolArray ;
2625use vortex_array:: arrays:: PrimitiveArray ;
2726use vortex_array:: dtype:: DType ;
2827use vortex_array:: dtype:: Nullability ;
2928use vortex_array:: dtype:: PType ;
29+ use vortex_array:: expr:: list_contains;
30+ use vortex_array:: expr:: lit;
31+ use vortex_array:: expr:: root;
3032use vortex_array:: scalar:: Scalar ;
31- use vortex_array:: scalar_fn :: fns :: list_contains :: ListContainsElementKernel ;
33+ use vortex_array:: session :: ArraySessionExt ;
3234use vortex_array:: validity:: Validity ;
3335use vortex_buffer:: Alignment ;
3436use vortex_buffer:: BufferMut ;
3537use vortex_fastlanes:: BitPacked ;
3638use vortex_fastlanes:: BitPackedArray ;
3739use vortex_fastlanes:: BitPackedData ;
38- use vortex_fastlanes:: list_contains_test_harness;
39- use vortex_fastlanes:: list_contains_test_harness:: MembershipSearch ;
40-
41- const LEN : usize = 64 * 1_024 ;
42- const MEMBER_COUNTS : & [ usize ] = & [ 1 , 2 , 3 , 4 , 6 , 8 , 9 , 12 , 16 , 24 , 32 , 64 ] ;
43- const STRATEGY_MEMBER_COUNTS : & [ usize ] = & [ 3 , 4 , 6 , 8 , 9 , 12 , 16 , 24 , 32 , 64 ] ;
40+ use vortex_session:: VortexSession ;
41+
42+ const DENSE_CASES : & [ ( usize , usize ) ] = & [
43+ ( 64 , 1 ) ,
44+ ( 64 , 4 ) ,
45+ ( 64 , 8 ) ,
46+ ( 64 , 32 ) ,
47+ ( 64 , 64 ) ,
48+ ( 1_024 , 1 ) ,
49+ ( 1_024 , 4 ) ,
50+ ( 1_024 , 8 ) ,
51+ ( 1_024 , 32 ) ,
52+ ( 1_024 , 64 ) ,
53+ ( 65_536 , 1 ) ,
54+ ( 65_536 , 4 ) ,
55+ ( 65_536 , 8 ) ,
56+ ( 65_536 , 32 ) ,
57+ ( 65_536 , 64 ) ,
58+ ] ;
59+ const SPARSE_CASES : & [ ( usize , usize ) ] = & [ ( 1_024 , 8 ) , ( 1_024 , 64 ) , ( 65_536 , 8 ) , ( 65_536 , 64 ) ] ;
60+ const DENSITY_CASES : & [ ( usize , usize , u32 ) ] = & [
61+ ( 64 , 5 , 1_000 ) ,
62+ ( 64 , 8 , 512 ) ,
63+ ( 64 , 64 , 64 ) ,
64+ ( 65_536 , 5 , 1_000 ) ,
65+ ( 65_536 , 8 , 512 ) ,
66+ ( 65_536 , 64 , 64 ) ,
67+ ] ;
4468
4569fn main ( ) {
4670 divan:: main ( ) ;
4771}
4872
49- fn members ( count : usize ) -> Vec < u32 > {
50- ( 0 ..count) . map ( |index| index as u32 * 2 ) . collect ( )
51- }
52-
53- fn mostly_missing_values ( ) -> Vec < u32 > {
54- ( 0 ..LEN )
55- . map ( |index| ( ( index as u32 * 17 ) % 4_096 ) | 1 )
56- . collect ( )
57- }
58-
59- fn mixed_values ( members : & [ u32 ] ) -> Vec < u32 > {
60- ( 0 ..LEN )
61- . map ( |index| {
62- if index. is_multiple_of ( 2 ) {
63- members[ ( index / 2 ) % members. len ( ) ]
64- } else {
65- ( ( index as u32 * 17 ) % 4_096 ) | 1
66- }
67- } )
68- . collect ( )
69- }
70-
71- fn count_linear ( values : & [ u32 ] , members : & [ u32 ] ) -> usize {
72- values
73- . iter ( )
74- . filter ( |value| members. contains ( black_box ( value) ) )
75- . count ( )
76- }
77-
78- fn count_binary ( values : & [ u32 ] , members : & [ u32 ] ) -> usize {
79- values
80- . iter ( )
81- . filter ( |value| members. binary_search ( black_box ( value) ) . is_ok ( ) )
82- . count ( )
83- }
84-
85- #[ divan:: bench( args = MEMBER_COUNTS ) ]
86- fn linear_mostly_missing ( bencher : Bencher , member_count : usize ) {
87- let values = mostly_missing_values ( ) ;
88- let members = members ( member_count) ;
89- bencher
90- . counter ( ItemsCount :: new ( LEN ) )
91- . bench_local ( || black_box ( count_linear ( & values, & members) ) ) ;
92- }
93-
94- #[ divan:: bench( args = MEMBER_COUNTS ) ]
95- fn binary_mostly_missing ( bencher : Bencher , member_count : usize ) {
96- let values = mostly_missing_values ( ) ;
97- let members = members ( member_count) ;
98- bencher
99- . counter ( ItemsCount :: new ( LEN ) )
100- . bench_local ( || black_box ( count_binary ( & values, & members) ) ) ;
101- }
102-
103- #[ divan:: bench( args = MEMBER_COUNTS ) ]
104- fn linear_mixed ( bencher : Bencher , member_count : usize ) {
105- let members = members ( member_count) ;
106- let values = mixed_values ( & members) ;
107- bencher
108- . counter ( ItemsCount :: new ( LEN ) )
109- . bench_local ( || black_box ( count_linear ( & values, & members) ) ) ;
110- }
111-
112- #[ divan:: bench( args = MEMBER_COUNTS ) ]
113- fn binary_mixed ( bencher : Bencher , member_count : usize ) {
114- let members = members ( member_count) ;
115- let values = mixed_values ( & members) ;
116- bencher
117- . counter ( ItemsCount :: new ( LEN ) )
118- . bench_local ( || black_box ( count_binary ( & values, & members) ) ) ;
73+ fn members ( count : usize , stride : u32 ) -> Vec < u32 > {
74+ ( 0 ..count) . map ( |index| index as u32 * stride) . collect ( )
11975}
12076
12177fn page_aligned ( array : BitPackedArray ) -> BitPackedArray {
@@ -133,9 +89,21 @@ fn page_aligned(array: BitPackedArray) -> BitPackedArray {
13389 . unwrap ( )
13490}
13591
136- fn kernel_inputs ( member_count : usize ) -> ( BitPackedArray , ArrayRef ) {
137- let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
138- let values: BufferMut < u32 > = ( 0 ..LEN ) . map ( |index| ( index as u32 * 17 ) % 1_024 ) . collect ( ) ;
92+ fn benchmark_input (
93+ len : usize ,
94+ member_count : usize ,
95+ member_stride : u32 ,
96+ specialized : bool ,
97+ ) -> ( ArrayRef , VortexSession ) {
98+ let session = array_session ( ) ;
99+ if specialized {
100+ vortex_fastlanes:: initialize ( & session) ;
101+ } else {
102+ session. arrays ( ) . register ( BitPacked ) ;
103+ }
104+
105+ let mut ctx = session. create_execution_ctx ( ) ;
106+ let values: BufferMut < u32 > = ( 0 ..len) . map ( |index| ( index as u32 * 17 ) % 1_024 ) . collect ( ) ;
139107 let packed = page_aligned (
140108 BitPackedData :: encode (
141109 & PrimitiveArray :: new ( values. freeze ( ) , Validity :: NonNullable ) . into_array ( ) ,
@@ -144,71 +112,62 @@ fn kernel_inputs(member_count: usize) -> (BitPackedArray, ArrayRef) {
144112 )
145113 . unwrap ( ) ,
146114 ) ;
147- let member_scalars = members ( member_count)
115+ let member_scalars = members ( member_count, member_stride )
148116 . into_iter ( )
149117 . map ( |value| Scalar :: primitive ( value, Nullability :: NonNullable ) )
150118 . collect ( ) ;
151- let list = ConstantArray :: new (
152- Scalar :: list (
153- Arc :: new ( DType :: Primitive ( PType :: U32 , Nullability :: NonNullable ) ) ,
154- member_scalars ,
155- Nullability :: NonNullable ,
156- ) ,
157- LEN ,
158- )
159- . into_array ( ) ;
160- ( packed , list )
119+ let list = Scalar :: list (
120+ Arc :: new ( DType :: Primitive ( PType :: U32 , Nullability :: NonNullable ) ) ,
121+ member_scalars ,
122+ Nullability :: NonNullable ,
123+ ) ;
124+ let contains = packed
125+ . into_array ( )
126+ . apply ( & list_contains ( lit ( list ) , root ( ) ) )
127+ . unwrap ( ) ;
128+ ( contains , session )
161129}
162130
163- #[ divan:: bench( args = MEMBER_COUNTS ) ]
164- fn bitpacked_kernel ( bencher : Bencher , member_count : usize ) {
165- let ( packed, list) = kernel_inputs ( member_count) ;
166- let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
167- bencher. counter ( ItemsCount :: new ( LEN ) ) . bench_local ( || {
168- black_box (
169- <BitPacked as ListContainsElementKernel >:: list_contains (
170- & list,
171- packed. as_view ( ) ,
172- & mut ctx,
173- )
174- . unwrap ( )
175- . unwrap ( ) ,
176- )
177- } ) ;
131+ fn bench_contains (
132+ bencher : Bencher ,
133+ len : usize ,
134+ member_count : usize ,
135+ member_stride : u32 ,
136+ specialized : bool ,
137+ ) {
138+ let ( contains, session) = benchmark_input ( len, member_count, member_stride, specialized) ;
139+ let mut ctx = session. create_execution_ctx ( ) ;
140+ bencher
141+ . counter ( ItemsCount :: new ( len) )
142+ . bench_local ( || black_box ( contains. clone ( ) . execute :: < BoolArray > ( & mut ctx) . unwrap ( ) ) ) ;
178143}
179144
180- #[ divan:: bench( args = STRATEGY_MEMBER_COUNTS ) ]
181- fn bitpacked_linear ( bencher : Bencher , member_count : usize ) {
182- let ( packed, list) = kernel_inputs ( member_count) ;
183- let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
184- bencher. counter ( ItemsCount :: new ( LEN ) ) . bench_local ( || {
185- black_box (
186- list_contains_test_harness:: list_contains (
187- & list,
188- packed. as_view ( ) ,
189- MembershipSearch :: Linear ,
190- & mut ctx,
191- )
192- . unwrap ( )
193- . unwrap ( ) ,
194- )
195- } ) ;
145+ #[ divan:: bench( args = DENSE_CASES ) ]
146+ fn compressed_dense ( bencher : Bencher , ( len, member_count) : ( usize , usize ) ) {
147+ bench_contains ( bencher, len, member_count, 2 , true ) ;
196148}
197149
198- #[ divan:: bench( args = STRATEGY_MEMBER_COUNTS ) ]
199- fn bitpacked_binary ( bencher : Bencher , member_count : usize ) {
200- let ( packed, list) = kernel_inputs ( member_count) ;
201- let mut ctx = array_session ( ) . create_execution_ctx ( ) ;
202- bencher. counter ( ItemsCount :: new ( LEN ) ) . bench_local ( || {
203- black_box (
204- list_contains_test_harness:: list_contains (
205- & list,
206- packed. as_view ( ) ,
207- MembershipSearch :: Binary ,
208- & mut ctx,
209- )
210- . unwrap ( )
211- . unwrap ( ) ,
212- )
213- } ) ;
150+ #[ divan:: bench( args = DENSE_CASES ) ]
151+ fn canonical_dense ( bencher : Bencher , ( len, member_count) : ( usize , usize ) ) {
152+ bench_contains ( bencher, len, member_count, 2 , false ) ;
153+ }
154+
155+ #[ divan:: bench( args = SPARSE_CASES ) ]
156+ fn compressed_sparse ( bencher : Bencher , ( len, member_count) : ( usize , usize ) ) {
157+ bench_contains ( bencher, len, member_count, 10_000 , true ) ;
158+ }
159+
160+ #[ divan:: bench( args = SPARSE_CASES ) ]
161+ fn canonical_sparse ( bencher : Bencher , ( len, member_count) : ( usize , usize ) ) {
162+ bench_contains ( bencher, len, member_count, 10_000 , false ) ;
163+ }
164+
165+ #[ divan:: bench( args = DENSITY_CASES ) ]
166+ fn compressed_density ( bencher : Bencher , ( len, member_count, member_stride) : ( usize , usize , u32 ) ) {
167+ bench_contains ( bencher, len, member_count, member_stride, true ) ;
168+ }
169+
170+ #[ divan:: bench( args = DENSITY_CASES ) ]
171+ fn canonical_density ( bencher : Bencher , ( len, member_count, member_stride) : ( usize , usize , u32 ) ) {
172+ bench_contains ( bencher, len, member_count, member_stride, false ) ;
214173}
0 commit comments