Skip to content

Commit e0ac2bd

Browse files
authored
Add benchmarks for take on runend array (#8469)
1 parent ea6f8fe commit e0ac2bd

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

encodings/runend/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ harness = false
5252
[[bench]]
5353
name = "run_end_decode"
5454
harness = false
55+
56+
[[bench]]
57+
name = "run_end_take"
58+
harness = false
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
#![expect(clippy::unwrap_used)]
5+
6+
use std::fmt;
7+
use std::sync::LazyLock;
8+
9+
use divan::Bencher;
10+
use rand::RngExt;
11+
use rand::SeedableRng;
12+
use rand::rngs::StdRng;
13+
use vortex_array::ArrayRef;
14+
use vortex_array::IntoArray;
15+
use vortex_array::RecursiveCanonical;
16+
use vortex_array::VortexSessionExecute;
17+
use vortex_array::arrays::PrimitiveArray;
18+
use vortex_array::session::ArraySession;
19+
use vortex_array::validity::Validity;
20+
use vortex_buffer::Buffer;
21+
use vortex_runend::RunEnd;
22+
use vortex_session::VortexSession;
23+
24+
fn main() {
25+
divan::main();
26+
}
27+
28+
static SESSION: LazyLock<VortexSession> =
29+
LazyLock::new(|| VortexSession::empty().with::<ArraySession>());
30+
31+
#[derive(Clone, Copy)]
32+
enum IndexPattern {
33+
SortedEven,
34+
ReverseDense,
35+
Random,
36+
}
37+
38+
#[derive(Clone, Copy)]
39+
enum IndexValidity {
40+
NonNullable,
41+
EveryFourthNull,
42+
AllNull,
43+
}
44+
45+
#[derive(Clone, Copy)]
46+
struct TakeBenchArgs {
47+
name: &'static str,
48+
array_len: usize,
49+
run_step: usize,
50+
take_len: usize,
51+
pattern: IndexPattern,
52+
validity: IndexValidity,
53+
}
54+
55+
impl fmt::Display for TakeBenchArgs {
56+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57+
write!(
58+
f,
59+
"{}_len{}_run{}_take{}",
60+
self.name, self.array_len, self.run_step, self.take_len
61+
)
62+
}
63+
}
64+
65+
const BENCH_ARGS: &[TakeBenchArgs] = &[
66+
// Sorted sparse takes should use the single-pass linear scan over run ends.
67+
TakeBenchArgs {
68+
name: "sorted_linear",
69+
array_len: 16_384,
70+
run_step: 8,
71+
take_len: 512,
72+
pattern: IndexPattern::SortedEven,
73+
validity: IndexValidity::NonNullable,
74+
},
75+
// Dense unsorted takes should build the logical-position-to-run table.
76+
TakeBenchArgs {
77+
name: "dense_table",
78+
array_len: 8_192,
79+
run_step: 1,
80+
take_len: 2_048,
81+
pattern: IndexPattern::ReverseDense,
82+
validity: IndexValidity::NonNullable,
83+
},
84+
// Sparse unsorted takes below the large-run threshold should stay on binary search.
85+
TakeBenchArgs {
86+
name: "binary_sparse",
87+
array_len: 65_536,
88+
run_step: 4,
89+
take_len: 512,
90+
pattern: IndexPattern::Random,
91+
validity: IndexValidity::NonNullable,
92+
},
93+
// Nullable indices exercise masked stats and table lookup.
94+
TakeBenchArgs {
95+
name: "nullable_dense_table",
96+
array_len: 8_192,
97+
run_step: 1,
98+
take_len: 2_048,
99+
pattern: IndexPattern::ReverseDense,
100+
validity: IndexValidity::EveryFourthNull,
101+
},
102+
// All-null indices should return before touching run ends or values.
103+
TakeBenchArgs {
104+
name: "all_null",
105+
array_len: 65_536,
106+
run_step: 4,
107+
take_len: 2_048,
108+
pattern: IndexPattern::Random,
109+
validity: IndexValidity::AllNull,
110+
},
111+
];
112+
113+
#[divan::bench(args = BENCH_ARGS)]
114+
fn take(bencher: Bencher, args: TakeBenchArgs) {
115+
let array = run_end_array(args.array_len, args.run_step);
116+
let indices = take_indices(args);
117+
118+
bencher
119+
.with_inputs(|| (&array, &indices, SESSION.create_execution_ctx()))
120+
.bench_refs(|(array, indices, execution_ctx)| {
121+
array
122+
.take(indices.clone())
123+
.unwrap()
124+
.execute::<RecursiveCanonical>(execution_ctx)
125+
.unwrap()
126+
});
127+
}
128+
129+
fn run_end_array(len: usize, run_step: usize) -> ArrayRef {
130+
let num_runs = len.div_ceil(run_step);
131+
let ends = (0..num_runs)
132+
.map(|run_idx| ((run_idx + 1) * run_step).min(len) as u64)
133+
.collect::<Buffer<_>>()
134+
.into_array();
135+
let values = PrimitiveArray::from_iter((0..num_runs).map(|idx| idx as u64)).into_array();
136+
137+
RunEnd::new(ends, values, &mut SESSION.create_execution_ctx()).into_array()
138+
}
139+
140+
fn take_indices(args: TakeBenchArgs) -> ArrayRef {
141+
let values = index_values(args);
142+
let validity = match args.validity {
143+
IndexValidity::NonNullable => Validity::NonNullable,
144+
IndexValidity::EveryFourthNull => {
145+
Validity::from_iter((0..args.take_len).map(|i| !i.is_multiple_of(4)))
146+
}
147+
IndexValidity::AllNull => Validity::AllInvalid,
148+
};
149+
150+
PrimitiveArray::new(values, validity).into_array()
151+
}
152+
153+
fn index_values(args: TakeBenchArgs) -> Buffer<u64> {
154+
let values = match args.pattern {
155+
IndexPattern::SortedEven => (0..args.take_len)
156+
.map(|idx| ((idx * args.array_len) / args.take_len) as u64)
157+
.collect::<Vec<_>>(),
158+
IndexPattern::ReverseDense => (0..args.take_len).rev().map(|idx| idx as u64).collect(),
159+
IndexPattern::Random => {
160+
let mut rng = StdRng::seed_from_u64(0);
161+
(0..args.take_len)
162+
.map(|_| rng.random_range(0..args.array_len) as u64)
163+
.collect()
164+
}
165+
};
166+
167+
Buffer::from(values)
168+
}

0 commit comments

Comments
 (0)