5050
5151use crate :: Rows ;
5252
53- /// When a bucket has this few elements, the fixed per-level cost of radix
54- /// sort (256-bucket histogram + scatter) exceeds the O(n log n) cost of
55- /// comparison sort with small n and warm cache lines.
56- const FALLBACK_THRESHOLD : usize = 64 ;
57-
58- /// Beyond this depth, comparison sort on the full row handles the
59- /// remaining discrimination. 8 bytes covers the discriminating prefix
60- /// of most key layouts; deeper recursion hits diminishing returns as
61- /// buckets become sparse and the per-level overhead dominates.
53+ /// Buckets smaller than this fall back to comparison sort. A lower
54+ /// threshold favors the comparison path, which avoids the ping-pong
55+ /// buffer overhead and per-level indirection cost of radix passes on
56+ /// small buckets where O(n log n) comparison sort is already cheap.
57+ const FALLBACK_THRESHOLD : usize = 32 ;
58+
59+ /// Maximum number of radix passes before falling back to comparison
60+ /// sort. Each pass chases pointers through the Rows offset/buffer
61+ /// indirection, so deeper passes hit diminishing returns as buckets
62+ /// shrink and the per-level overhead dominates. 8 bytes covers the
63+ /// discriminating prefix of most key layouts including skewed or
64+ /// narrow-range distributions; remaining ties are resolved by
65+ /// comparison sort on the suffix.
6266const MAX_DEPTH : usize = 8 ;
6367
6468/// Sort row indices using MSD radix sort on row-encoded keys.
@@ -90,16 +94,46 @@ const MAX_DEPTH: usize = 8;
9094/// [`take`]: https://docs.rs/arrow/latest/arrow/compute/fn.take.html
9195/// [`lexsort_to_indices`]: https://docs.rs/arrow-ord/latest/arrow_ord/sort/fn.lexsort_to_indices.html
9296pub fn radix_sort_to_indices ( rows : & Rows ) -> Vec < u32 > {
97+ radix_sort_to_indices_with ( rows, MAX_DEPTH , FALLBACK_THRESHOLD )
98+ }
99+
100+ /// Like [`radix_sort_to_indices`] but with tunable parameters for
101+ /// benchmarking. `max_depth` controls how many radix passes to run
102+ /// before falling back to comparison sort. `fallback_threshold` is
103+ /// the bucket size below which we switch to comparison sort.
104+ pub fn radix_sort_to_indices_with (
105+ rows : & Rows ,
106+ max_depth : usize ,
107+ fallback_threshold : usize ,
108+ ) -> Vec < u32 > {
93109 let n = rows. num_rows ( ) ;
94110 let mut indices: Vec < u32 > = ( 0 ..n as u32 ) . collect ( ) ;
95111 let mut temp = vec ! [ 0u32 ; n] ;
96112 let mut bytes = vec ! [ 0u8 ; n] ;
97- msd_radix_sort ( & mut indices, & mut temp, & mut bytes, rows, 0 , true ) ;
113+ let config = RadixSortConfig {
114+ max_depth,
115+ fallback_threshold,
116+ } ;
117+ msd_radix_sort ( & mut indices, & mut temp, & mut bytes, rows, 0 , true , & config) ;
98118 indices
99119}
100120
101- /// Returns the byte at `byte_pos` for the row at `idx`, or 0 if the row
102- /// is shorter.
121+ /// Tunable parameters for MSD radix sort.
122+ struct RadixSortConfig {
123+ /// Maximum number of radix passes before falling back to comparison sort.
124+ max_depth : usize ,
125+ /// Buckets smaller than this fall back to comparison sort.
126+ fallback_threshold : usize ,
127+ }
128+
129+ impl Default for RadixSortConfig {
130+ fn default ( ) -> Self {
131+ Self {
132+ max_depth : MAX_DEPTH ,
133+ fallback_threshold : FALLBACK_THRESHOLD ,
134+ }
135+ }
136+ }
103137///
104138/// # Safety
105139/// `idx` must be a valid row index in `rows`.
@@ -126,10 +160,11 @@ fn msd_radix_sort(
126160 rows : & Rows ,
127161 byte_pos : usize ,
128162 result_in_src : bool ,
163+ config : & RadixSortConfig ,
129164) {
130165 let n = src. len ( ) ;
131166
132- if n <= FALLBACK_THRESHOLD || byte_pos >= MAX_DEPTH {
167+ if n <= config . fallback_threshold || byte_pos >= config . max_depth {
133168 // Compare only from byte_pos onward — earlier bytes are identical
134169 // within this bucket, having already been discriminated by radix
135170 // passes above us. Safe slice via get() is needed because rows of
@@ -178,7 +213,7 @@ fn msd_radix_sort(
178213
179214 // No scatter happened — data is still in src, roles unchanged.
180215 if num_buckets == 1 {
181- msd_radix_sort ( src, dst, bytes, rows, byte_pos + 1 , result_in_src) ;
216+ msd_radix_sort ( src, dst, bytes, rows, byte_pos + 1 , result_in_src, config ) ;
182217 return ;
183218 }
184219
@@ -205,6 +240,7 @@ fn msd_radix_sort(
205240 rows,
206241 byte_pos + 1 ,
207242 !result_in_src,
243+ config,
208244 ) ;
209245 } else if len == 1 && result_in_src {
210246 // Single-element bucket doesn't recurse. After scatter
0 commit comments