-
Notifications
You must be signed in to change notification settings - Fork 173
Expand file tree
/
Copy pathchunk_array_builder.rs
More file actions
275 lines (241 loc) · 10.3 KB
/
Copy pathchunk_array_builder.rs
File metadata and controls
275 lines (241 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors
use std::sync::LazyLock;
use divan::Bencher;
use rand::RngExt;
use rand::SeedableRng;
use rand::prelude::StdRng;
use vortex_array::ArrayRef;
use vortex_array::Canonical;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
use vortex_array::arrays::BoolArray;
use vortex_array::arrays::ChunkedArray;
use vortex_array::arrays::ConstantArray;
// `ArrayBuilder`, `VarBinViewBuilder`, and `DType` are used only by the VarBinView canonicalization
// benches and their helpers, all of which are excluded from CodSpeed (see the gating note below), so
// they are gated to match and avoid unused-import errors under `--cfg codspeed`.
#[cfg(not(codspeed))]
use vortex_array::builders::ArrayBuilder;
#[cfg(not(codspeed))]
use vortex_array::builders::VarBinViewBuilder;
use vortex_array::builders::builder_with_capacity;
#[cfg(not(codspeed))]
use vortex_array::dtype::DType;
use vortex_error::VortexExpect;
use vortex_session::VortexSession;
fn main() {
divan::main();
}
const BENCH_ARGS: &[(usize, usize)] = &[
// length, chunk_count
(10, 1000),
(100, 100),
(1000, 10),
];
static SESSION: LazyLock<VortexSession> = LazyLock::new(vortex_array::array_session);
// The canonicalization benchmarks gated below are excluded from CodSpeed's CPU simulation. Their
// simulated instruction count is dominated by output-buffer allocation and glibc `memcpy`/`memmove`
// (whose `ifunc`-selected implementation varies across runner images) rather than by Vortex compute,
// so under simulation they report spurious, bidirectional regressions even when the code is
// unchanged. `chunked_bool_canonical_into` is additionally small enough to sit near the simulation
// noise floor. They cannot be stabilized by tuning inputs because the data movement is the thing
// being measured. The `chunked_opt_bool_*` and `chunked_constant_*` benches below are compute-bound
// and stable under simulation, so they are kept. Per `docs/developer-guide/benchmarking.md` such
// benchmarks are gated with `#[cfg(not(codspeed))]` and remain available via local `cargo bench`.
// See https://github.com/vortex-data/vortex/pull/8519 for the supporting analysis.
#[cfg(not(codspeed))]
#[divan::bench(args = BENCH_ARGS)]
fn chunked_bool_canonical_into(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunk = make_bool_chunks(len, chunk_count);
bencher
.with_inputs(|| (&chunk, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| {
let mut builder = builder_with_capacity(chunk.dtype(), len * chunk_count);
chunk
.append_to_builder(builder.as_mut(), ctx)
.vortex_expect("append failed");
builder.finish()
})
}
#[divan::bench(args = BENCH_ARGS)]
fn chunked_opt_bool_canonical_into(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunk = make_opt_bool_chunks(len, chunk_count);
bencher
.with_inputs(|| (&chunk, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| {
let mut builder = builder_with_capacity(chunk.dtype(), len * chunk_count);
chunk
.append_to_builder(builder.as_mut(), ctx)
.vortex_expect("append failed");
builder.finish()
})
}
#[divan::bench(args = BENCH_ARGS)]
fn chunked_opt_bool_into_canonical(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunk = make_opt_bool_chunks(len, chunk_count);
bencher
.with_inputs(|| (&chunk, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| (**chunk).clone().execute::<Canonical>(ctx))
}
// Excluded from CodSpeed: VarBinView canonicalization is `memcpy`-bound (see note above).
#[cfg(not(codspeed))]
#[divan::bench(args = BENCH_ARGS)]
fn chunked_varbinview_canonical_into(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunks = make_string_chunks(false, len, chunk_count);
bencher
.with_inputs(|| (&chunks, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| {
let mut builder = VarBinViewBuilder::with_capacity(
DType::Utf8(chunk.dtype().nullability()),
len * chunk_count,
);
chunk
.append_to_builder(&mut builder, ctx)
.vortex_expect("append failed");
builder.finish()
})
}
// Excluded from CodSpeed: VarBinView canonicalization is `memcpy`-bound (see note above).
#[cfg(not(codspeed))]
#[divan::bench(args = BENCH_ARGS)]
fn chunked_varbinview_into_canonical(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunks = make_string_chunks(false, len, chunk_count);
bencher
.with_inputs(|| (&chunks, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| (**chunk).clone().execute::<Canonical>(ctx))
}
// Excluded from CodSpeed: VarBinView canonicalization is `memcpy`-bound (see note above).
#[cfg(not(codspeed))]
#[divan::bench(args = BENCH_ARGS)]
fn chunked_varbinview_opt_canonical_into(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunks = make_string_chunks(true, len, chunk_count);
bencher
.with_inputs(|| (&chunks, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| {
let mut builder = VarBinViewBuilder::with_capacity(
DType::Utf8(chunk.dtype().nullability()),
len * chunk_count,
);
chunk
.append_to_builder(&mut builder, ctx)
.vortex_expect("append failed");
builder.finish()
})
}
// Excluded from CodSpeed: VarBinView canonicalization is `memcpy`-bound (see note above).
#[cfg(not(codspeed))]
#[divan::bench(args = BENCH_ARGS)]
fn chunked_varbinview_opt_into_canonical(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunks = make_string_chunks(true, len, chunk_count);
bencher
.with_inputs(|| (&chunks, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| (**chunk).clone().execute::<Canonical>(ctx))
}
#[divan::bench(args = BENCH_ARGS)]
fn chunked_constant_i32_append_to_builder(bencher: Bencher, (len, chunk_count): (usize, usize)) {
let chunk = make_constant_i32_chunks(len, chunk_count);
bencher
.with_inputs(|| (&chunk, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| {
let mut builder = builder_with_capacity(chunk.dtype(), len * chunk_count);
chunk
.append_to_builder(builder.as_mut(), ctx)
.vortex_expect("append failed");
builder.finish()
})
}
const CONSTANT_UTF8_BENCH_ARGS: &[(&str, usize, usize)] = &[
// value, length, chunk_count
("hi", 1000, 10), // inline (≤12 bytes)
("hello world!!", 1000, 10), // non-inline (>12 bytes)
];
#[divan::bench(args = CONSTANT_UTF8_BENCH_ARGS)]
fn chunked_constant_utf8_append_to_builder(
bencher: Bencher,
(value, len, chunk_count): (&str, usize, usize),
) {
let chunk = make_constant_utf8_chunks(value, len, chunk_count);
bencher
.with_inputs(|| (&chunk, SESSION.create_execution_ctx()))
.bench_refs(|(chunk, ctx)| {
let mut builder = builder_with_capacity(chunk.dtype(), len * chunk_count);
chunk
.append_to_builder(builder.as_mut(), ctx)
.vortex_expect("append failed");
builder.finish()
})
}
fn make_constant_utf8_chunks(value: &str, len: usize, chunk_count: usize) -> ArrayRef {
use vortex_array::dtype::Nullability;
use vortex_array::scalar::Scalar;
(0..chunk_count)
.map(|_| {
ConstantArray::new(Scalar::utf8(value, Nullability::NonNullable), len).into_array()
})
.collect::<ChunkedArray>()
.into_array()
}
fn make_constant_i32_chunks(len: usize, chunk_count: usize) -> ArrayRef {
// Each chunk is a ConstantArray of i32; dtype is I32/NonNullable via From<i32> for Scalar.
(0..chunk_count)
.map(|_| ConstantArray::new(42i32, len).into_array())
.collect::<ChunkedArray>()
.into_array()
}
fn make_opt_bool_chunks(len: usize, chunk_count: usize) -> ArrayRef {
let mut rng = StdRng::seed_from_u64(0);
const SPAN_LEN: usize = 10;
assert!(len.is_multiple_of(SPAN_LEN));
(0..chunk_count)
.map(|_| {
BoolArray::from_iter(
(0..len / SPAN_LEN)
.flat_map(|_| match rng.random_range::<u8, _>(0..=2) {
0 => vec![Some(false); SPAN_LEN],
1 => vec![Some(true); SPAN_LEN],
2 => vec![None; SPAN_LEN],
_ => unreachable!(),
})
// To get a sized iterator
.collect::<Vec<Option<bool>>>(),
)
.into_array()
})
.collect::<ChunkedArray>()
.into_array()
}
// Only used by `chunked_bool_canonical_into`, which is excluded from CodSpeed (see above), so this
// helper is gated to match and avoid dead-code errors under `--cfg codspeed`.
#[cfg(not(codspeed))]
fn make_bool_chunks(len: usize, chunk_count: usize) -> ArrayRef {
let mut rng = StdRng::seed_from_u64(0);
(0..chunk_count)
.map(|_| BoolArray::from_iter((0..len).map(|_| rng.random_bool(0.5))).into_array())
.collect::<ChunkedArray>()
.into_array()
}
// Only used by the gated VarBinView canonicalization benches above, which are excluded from
// CodSpeed, so this helper is gated to match and avoid dead-code errors under `--cfg codspeed`.
#[cfg(not(codspeed))]
fn make_string_chunks(nullable: bool, len: usize, chunk_count: usize) -> ArrayRef {
let mut rng = StdRng::seed_from_u64(123);
(0..chunk_count)
.map(|_| {
let mut builder = VarBinViewBuilder::with_capacity(DType::Utf8(nullable.into()), len);
(0..len).for_each(|_| {
if nullable && rng.random_bool(0.2) {
builder.append_null()
} else {
builder.append_value(
(0..rng.random_range(0..=20))
.map(|_| rng.random_range(b'a'..=b'z'))
.collect::<Vec<u8>>(),
)
}
});
builder.finish()
})
.collect::<ChunkedArray>()
.into_array()
}