Skip to content

Commit 4c4498f

Browse files
committed
Update defaults based on sensitivity analysis.
1 parent 15351fe commit 4c4498f

File tree

1 file changed

+50
-14
lines changed

1 file changed

+50
-14
lines changed

arrow-row/src/radix.rs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,19 @@
5050
5151
use 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.
6266
const 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
9296
pub 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

Comments
 (0)