@@ -13,8 +13,9 @@ use vortex_array::IntoArray;
1313use vortex_array:: VortexSessionExecute ;
1414use vortex_array:: arrays:: BoolArray ;
1515use vortex_array:: arrays:: ConstantArray ;
16+ use vortex_array:: arrays:: ScalarFnArray ;
1617use vortex_array:: arrays:: VarBinViewArray ;
17- use vortex_array:: arrays :: scalar_fn:: ScalarFnFactoryExt ;
18+ use vortex_array:: scalar_fn:: TypedScalarFnInstance ;
1819use vortex_array:: scalar_fn:: fns:: like:: Like ;
1920use vortex_array:: scalar_fn:: fns:: like:: LikeOptions ;
2021
@@ -24,11 +25,16 @@ fn main() {
2425
2526const ARRAY_SIZE : usize = 2_048 ;
2627
28+ /// The per-row pattern arms compile one pattern per row in the worst case, which costs an order of
29+ /// magnitude more per row than matching against a constant pattern. They use a smaller fixture to
30+ /// stay inside the 1 ms per-iteration budget from `docs/developer-guide/benchmarking.md`.
31+ const PER_ROW_PATTERN_ROWS : usize = 512 ;
32+
2733/// Random lowercase strings of 4..=24 bytes, some with a `hello` infix.
28- fn strings ( ) -> ArrayRef {
34+ fn strings ( len : usize ) -> ArrayRef {
2935 let mut rng = StdRng :: seed_from_u64 ( 0 ) ;
3036 let len_dist = Uniform :: new_inclusive ( 4usize , 24 ) . unwrap ( ) ;
31- VarBinViewArray :: from_iter_str ( ( 0 ..ARRAY_SIZE ) . map ( |i| {
37+ VarBinViewArray :: from_iter_str ( ( 0 ..len ) . map ( |i| {
3238 let len = rng. sample ( len_dist) ;
3339 let mut s: String = ( 0 ..len)
3440 . map ( |_| char:: from ( rng. random_range ( b'a' ..=b'z' ) ) )
@@ -43,19 +49,19 @@ fn strings() -> ArrayRef {
4349
4450fn bench_like ( bencher : Bencher , pattern : & str , options : LikeOptions ) {
4551 let session = vortex_array:: array_session ( ) ;
46- let array = strings ( ) ;
52+ let array = strings ( ARRAY_SIZE ) ;
4753 bencher
4854 . with_inputs ( || {
4955 (
50- Like . try_new_array (
51- ARRAY_SIZE ,
52- options,
53- [
56+ ScalarFnArray :: try_new (
57+ TypedScalarFnInstance :: new ( Like , options) . erased ( ) ,
58+ vec ! [
5459 array. clone( ) ,
5560 ConstantArray :: new( pattern, ARRAY_SIZE ) . into_array( ) ,
5661 ] ,
5762 )
58- . unwrap ( ) ,
63+ . unwrap ( )
64+ . into_array ( ) ,
5965 session. create_execution_ctx ( ) ,
6066 )
6167 } )
@@ -87,28 +93,63 @@ fn like_regex(bencher: Bencher) {
8793 bench_like ( bencher, "h_llo%w%d" , LikeOptions :: default ( ) ) ;
8894}
8995
90- #[ divan:: bench]
91- fn like_per_row_patterns ( bencher : Bencher ) {
96+ fn bench_per_row_patterns ( bencher : Bencher , patterns : ArrayRef ) {
9297 let session = vortex_array:: array_session ( ) ;
93- let array = strings ( ) ;
94- // A non-constant pattern child takes the per-row path; repeated patterns hit the
95- // compile cache.
96- let patterns = VarBinViewArray :: from_iter_str ( ( 0 ..ARRAY_SIZE ) . map ( |_| "hello%" ) ) . into_array ( ) ;
98+ let array = strings ( patterns. len ( ) ) ;
9799 bencher
98100 . with_inputs ( || {
99101 (
100- Like . try_new_array (
101- ARRAY_SIZE ,
102- LikeOptions :: default ( ) ,
103- [ array. clone ( ) , patterns. clone ( ) ] ,
102+ ScalarFnArray :: try_new (
103+ TypedScalarFnInstance :: new ( Like , LikeOptions :: default ( ) ) . erased ( ) ,
104+ vec ! [ array. clone( ) , patterns. clone( ) ] ,
104105 )
105- . unwrap ( ) ,
106+ . unwrap ( )
107+ . into_array ( ) ,
106108 session. create_execution_ctx ( ) ,
107109 )
108110 } )
109111 . bench_values ( |( array, mut ctx) | array. execute :: < BoolArray > ( & mut ctx) . unwrap ( ) ) ;
110112}
111113
114+ #[ divan:: bench]
115+ fn like_per_row_patterns ( bencher : Bencher ) {
116+ // A non-constant pattern child takes the per-row path; repeated patterns hit the compile cache.
117+ let patterns = VarBinViewArray :: from_iter_str ( ( 0 ..ARRAY_SIZE ) . map ( |_| "hello%" ) ) . into_array ( ) ;
118+ bench_per_row_patterns ( bencher, patterns) ;
119+ }
120+
121+ /// The cached half of the compile-cache pair: every row repeats one pattern whose five-byte shape
122+ /// matches the distinct-pattern arm, so the two differ only in whether the cache hits. Both use
123+ /// [`PER_ROW_PATTERN_ROWS`] rows to stay comparable.
124+ ///
125+ /// [`like_per_row_distinct_patterns`]
126+ #[ divan:: bench]
127+ fn like_per_row_repeated_patterns ( bencher : Bencher ) {
128+ let patterns =
129+ VarBinViewArray :: from_iter_str ( ( 0 ..PER_ROW_PATTERN_ROWS ) . map ( |_| "%aaa%" ) ) . into_array ( ) ;
130+ bench_per_row_patterns ( bencher, patterns) ;
131+ }
132+
133+ /// The per-row path with the compile cache defeated: every row carries a distinct pattern of the
134+ /// same shape as the repeated-pattern arm, so each row pays one pattern compilation.
135+ ///
136+ /// [`like_per_row_repeated_patterns`]
137+ #[ divan:: bench]
138+ fn like_per_row_distinct_patterns ( bencher : Bencher ) {
139+ let patterns = VarBinViewArray :: from_iter_str (
140+ ( 0 ..PER_ROW_PATTERN_ROWS ) . map ( |i| format ! ( "%{}%" , distinct_trigram( i) ) ) ,
141+ )
142+ . into_array ( ) ;
143+ bench_per_row_patterns ( bencher, patterns) ;
144+ }
145+
146+ /// A distinct three-letter lowercase infix per row, so every pattern has the same shape while
147+ /// no two rows share a compiled pattern.
148+ fn distinct_trigram ( i : usize ) -> String {
149+ let letter = |shift : usize | char:: from ( b'a' + u8:: try_from ( ( i >> shift) % 26 ) . unwrap ( ) ) ;
150+ [ letter ( 0 ) , letter ( 5 ) , letter ( 10 ) ] . iter ( ) . collect ( )
151+ }
152+
112153#[ divan:: bench]
113154fn ilike_contains ( bencher : Bencher ) {
114155 bench_like (
0 commit comments