2121//! `PartitionKey` (`Vec<ScalarValue>`) and, in `Linear` mode (input sorted
2222//! by the ORDER BY column but not by the partition columns), visits every
2323//! live partition on every batch while never retiring partitions until the
24- //! input is exhausted. The cases here stress that path in different ways:
24+ //! input is exhausted. The cases here stress that path in different ways.
2525//!
26- //! - `linear N partitions`: dense round-robin keys -- every partition
27- //! receives rows in every batch, so per-visit fixed costs dominate.
28- //! - `linear sparse N partitions`: keys are clustered in time, so each
29- //! batch touches only a small, fresh subset of keys while the set of live
30- //! partitions keeps growing -- per-batch work on quiet partitions
26+ //! Case names spell out the input order mode (`linear` / `sorted`), the key
27+ //! layout (`dense` / `sparse`), the window functions, an optional frame
28+ //! variant, and the partition count:
29+ //!
30+ //! - `linear dense count N partitions`: dense round-robin keys -- every
31+ //! partition receives rows in every batch, so per-visit fixed costs
32+ //! dominate.
33+ //! - `linear sparse count N partitions`: keys are clustered in time, so
34+ //! each batch touches only a small, fresh subset of keys while the set of
35+ //! live partitions keeps growing -- per-batch work on quiet partitions
3136//! dominates.
32- //! - `linear rows N partitions`: the dense layout with a ROWS frame, whose
33- //! results can only be finalized as more rows of the same partition
34- //! arrive.
35- //! - `linear multi N partitions`: two window expressions over the dense
36- //! layout, doubling the per-partition evaluation sweeps.
37- //! - `sorted N partitions`: control; input sorted by partition key, so
38- //! finished partitions are pruned eagerly and the state maps stay small.
37+ //! - `linear dense count rows-frame N partitions`: the dense layout with a
38+ //! ROWS frame, whose results can only be finalized as more rows of the
39+ //! same partition arrive.
40+ //! - `linear dense count+sum N partitions`: two window expressions over the
41+ //! dense layout, doubling the per-partition evaluation sweeps.
42+ //! - `linear dense row_number N partitions` / `linear sparse row_number N
43+ //! partitions`: the dense / sparse layouts evaluated through
44+ //! `StandardWindowExpr` and a `PartitionEvaluator` rather than an
45+ //! aggregate accumulator.
46+ //! - `linear sparse lead N partitions`: the sparse layout with a non-causal
47+ //! function, whose result for the last buffered row of a partition stays
48+ //! pending until that partition receives another row.
49+ //! - `linear dense rank N partitions`: the dense layout with an evaluator
50+ //! that compares ORDER BY values row by row.
51+ //! - `sorted count N partitions`: control; input sorted by partition key,
52+ //! as `Sorted` mode requires, so finished partitions are pruned eagerly
53+ //! and the state maps stay small.
3954
4055use std:: sync:: Arc ;
4156
@@ -50,8 +65,11 @@ use datafusion_expr::{
5065} ;
5166use datafusion_functions_aggregate:: count:: count_udaf;
5267use datafusion_functions_aggregate:: sum:: sum_udaf;
68+ use datafusion_functions_window:: lead_lag:: lead_udwf;
69+ use datafusion_functions_window:: rank:: rank_udwf;
70+ use datafusion_functions_window:: row_number:: row_number_udwf;
5371use datafusion_physical_expr:: expressions:: col;
54- use datafusion_physical_expr:: { LexOrdering , PhysicalSortExpr } ;
72+ use datafusion_physical_expr:: { LexOrdering , PhysicalExpr , PhysicalSortExpr } ;
5573use datafusion_physical_plan:: test:: TestMemoryExec ;
5674use datafusion_physical_plan:: windows:: { BoundedWindowAggExec , create_window_expr} ;
5775use datafusion_physical_plan:: { ExecutionPlan , InputOrderMode , collect} ;
@@ -135,34 +153,46 @@ fn rows_frame() -> WindowFrame {
135153 )
136154}
137155
138- /// `<agg>(ts) OVER (PARTITION BY pk ORDER BY ts <window_frame>)` for each
139- /// aggregate in `aggregates`.
156+ /// `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`, the default frame
157+ /// of a window that has an ORDER BY clause.
158+ fn default_frame ( ) -> WindowFrame {
159+ WindowFrame :: new ( Some ( false ) )
160+ }
161+
162+ /// A window function to benchmark: definition, display name, and arguments.
163+ type BenchWindowFn = (
164+ WindowFunctionDefinition ,
165+ & ' static str ,
166+ Vec < Arc < dyn PhysicalExpr > > ,
167+ ) ;
168+
169+ /// `<fn>(<args>) OVER (PARTITION BY pk ORDER BY ts <window_frame>)` for each
170+ /// window function in `functions`.
140171fn window_exec (
141172 batches : Vec < RecordBatch > ,
142173 mode : InputOrderMode ,
143174 input_ordering : Vec < PhysicalSortExpr > ,
144175 window_frame : & WindowFrame ,
145- aggregates : & [ ( WindowFunctionDefinition , & str ) ] ,
176+ functions : & [ BenchWindowFn ] ,
146177) -> Arc < dyn ExecutionPlan > {
147178 let schema = schema ( ) ;
148179 let source = TestMemoryExec :: try_new ( & [ batches] , Arc :: clone ( & schema) , None )
149180 . expect ( "memory exec" )
150181 . try_with_sort_information ( LexOrdering :: new ( input_ordering) . into_iter ( ) . collect ( ) )
151182 . expect ( "sort information" ) ;
152183 let input = Arc :: new ( TestMemoryExec :: update_cache ( & Arc :: new ( source) ) ) ;
153- let args = vec ! [ col( "ts" , & schema) . unwrap( ) ] ;
154184 let partitionby_exprs = vec ! [ col( "pk" , & schema) . unwrap( ) ] ;
155185 let orderby_exprs = vec ! [ PhysicalSortExpr {
156186 expr: col( "ts" , & schema) . unwrap( ) ,
157187 options: Default :: default ( ) ,
158188 } ] ;
159- let window_expr = aggregates
189+ let window_expr = functions
160190 . iter ( )
161- . map ( |( fun, name) | {
191+ . map ( |( fun, name, args ) | {
162192 create_window_expr (
163193 fun,
164194 name. to_string ( ) ,
165- & args,
195+ args,
166196 & partitionby_exprs,
167197 & orderby_exprs,
168198 Arc :: new ( window_frame. clone ( ) ) ,
@@ -180,15 +210,48 @@ fn window_exec(
180210 )
181211}
182212
183- fn count ( ) -> ( WindowFunctionDefinition , & ' static str ) {
213+ fn ts_arg ( ) -> Vec < Arc < dyn PhysicalExpr > > {
214+ vec ! [ col( "ts" , & schema( ) ) . unwrap( ) ]
215+ }
216+
217+ fn count ( ) -> BenchWindowFn {
184218 (
185219 WindowFunctionDefinition :: AggregateUDF ( count_udaf ( ) ) ,
186220 "count" ,
221+ ts_arg ( ) ,
187222 )
188223}
189224
190- fn sum ( ) -> ( WindowFunctionDefinition , & ' static str ) {
191- ( WindowFunctionDefinition :: AggregateUDF ( sum_udaf ( ) ) , "sum" )
225+ fn sum ( ) -> BenchWindowFn {
226+ (
227+ WindowFunctionDefinition :: AggregateUDF ( sum_udaf ( ) ) ,
228+ "sum" ,
229+ ts_arg ( ) ,
230+ )
231+ }
232+
233+ fn row_number ( ) -> BenchWindowFn {
234+ (
235+ WindowFunctionDefinition :: WindowUDF ( row_number_udwf ( ) ) ,
236+ "row_number" ,
237+ vec ! [ ] ,
238+ )
239+ }
240+
241+ fn lead ( ) -> BenchWindowFn {
242+ (
243+ WindowFunctionDefinition :: WindowUDF ( lead_udwf ( ) ) ,
244+ "lead" ,
245+ ts_arg ( ) ,
246+ )
247+ }
248+
249+ fn rank ( ) -> BenchWindowFn {
250+ (
251+ WindowFunctionDefinition :: WindowUDF ( rank_udwf ( ) ) ,
252+ "rank" ,
253+ vec ! [ ] ,
254+ )
192255}
193256
194257fn bounded_window_benchmark ( c : & mut Criterion ) {
@@ -213,7 +276,7 @@ fn bounded_window_benchmark(c: &mut Criterion) {
213276
214277 for n_partitions in [ 100 , 10_000 ] {
215278 run_case (
216- format ! ( "linear {n_partitions} partitions" ) ,
279+ format ! ( "linear dense count {n_partitions} partitions" ) ,
217280 window_exec (
218281 dense_batches ( n_partitions) ,
219282 InputOrderMode :: Linear ,
@@ -226,7 +289,7 @@ fn bounded_window_benchmark(c: &mut Criterion) {
226289
227290 run_case (
228291 format ! (
229- "linear sparse {} partitions" ,
292+ "linear sparse count {} partitions" ,
230293 N_BATCHES * SPARSE_KEYS_PER_BATCH
231294 ) ,
232295 window_exec (
@@ -239,7 +302,7 @@ fn bounded_window_benchmark(c: &mut Criterion) {
239302 ) ;
240303
241304 run_case (
242- "linear rows 10000 partitions" . to_string ( ) ,
305+ "linear dense count rows-frame 10000 partitions" . to_string ( ) ,
243306 window_exec (
244307 dense_batches ( 10_000 ) ,
245308 InputOrderMode :: Linear ,
@@ -250,7 +313,7 @@ fn bounded_window_benchmark(c: &mut Criterion) {
250313 ) ;
251314
252315 run_case (
253- "linear multi 10000 partitions" . to_string ( ) ,
316+ "linear dense count+sum 10000 partitions" . to_string ( ) ,
254317 window_exec (
255318 dense_batches ( 10_000 ) ,
256319 InputOrderMode :: Linear ,
@@ -260,10 +323,60 @@ fn bounded_window_benchmark(c: &mut Criterion) {
260323 ) ,
261324 ) ;
262325
326+ run_case (
327+ "linear dense row_number 10000 partitions" . to_string ( ) ,
328+ window_exec (
329+ dense_batches ( 10_000 ) ,
330+ InputOrderMode :: Linear ,
331+ vec ! [ sort_expr( "ts" ) ] ,
332+ & default_frame ( ) ,
333+ & [ row_number ( ) ] ,
334+ ) ,
335+ ) ;
336+
337+ run_case (
338+ format ! (
339+ "linear sparse row_number {} partitions" ,
340+ N_BATCHES * SPARSE_KEYS_PER_BATCH
341+ ) ,
342+ window_exec (
343+ sparse_batches ( ) ,
344+ InputOrderMode :: Linear ,
345+ vec ! [ sort_expr( "ts" ) ] ,
346+ & default_frame ( ) ,
347+ & [ row_number ( ) ] ,
348+ ) ,
349+ ) ;
350+
351+ run_case (
352+ format ! (
353+ "linear sparse lead {} partitions" ,
354+ N_BATCHES * SPARSE_KEYS_PER_BATCH
355+ ) ,
356+ window_exec (
357+ sparse_batches ( ) ,
358+ InputOrderMode :: Linear ,
359+ vec ! [ sort_expr( "ts" ) ] ,
360+ & default_frame ( ) ,
361+ & [ lead ( ) ] ,
362+ ) ,
363+ ) ;
364+
365+ run_case (
366+ "linear dense rank 10000 partitions" . to_string ( ) ,
367+ window_exec (
368+ dense_batches ( 10_000 ) ,
369+ InputOrderMode :: Linear ,
370+ vec ! [ sort_expr( "ts" ) ] ,
371+ & default_frame ( ) ,
372+ & [ rank ( ) ] ,
373+ ) ,
374+ ) ;
375+
263376 // Control: the same query over partition-sorted input, where finished
264377 // partitions are pruned eagerly and the state maps stay small.
265378 run_case (
266- "sorted 10000 partitions" . to_string ( ) ,
379+ "sorted count 10000 partitions" . to_string ( ) ,
267380 window_exec (
268381 sorted_batches ( 10_000 ) ,
269382 InputOrderMode :: Sorted ,
0 commit comments