Skip to content

Commit fac0eca

Browse files
committed
perf: optimize TPC-DS runtime pruning and run-end kernels
Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
1 parent 731a231 commit fac0eca

9 files changed

Lines changed: 1202 additions & 80 deletions

File tree

‎encodings/runend/benches/run_end_decode.rs‎

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@ use mimalloc::MiMalloc;
1111
use vortex_array::VortexSessionExecute;
1212
use vortex_array::arrays::BoolArray;
1313
use vortex_array::arrays::PrimitiveArray;
14+
use vortex_array::dtype::NativePType;
15+
use vortex_array::dtype::Nullability;
1416
use vortex_array::validity::Validity;
1517
use vortex_buffer::BitBuffer;
18+
use vortex_buffer::Buffer;
1619
use vortex_buffer::BufferMut;
20+
use vortex_error::VortexExpect;
21+
use vortex_mask::Mask;
22+
use vortex_runend::compress::runend_decode_primitive;
23+
use vortex_runend::compress::runend_decode_typed_primitive;
1724
use vortex_runend::decompress_bool::runend_decode_bools;
1825
use vortex_session::VortexSession;
1926

@@ -389,3 +396,101 @@ fn decode_bool_nullable(bencher: Bencher, args: NullableBoolBenchArgs) {
389396
runend_decode_bools(ends.clone(), values.clone(), 0, total_length, ctx)
390397
});
391398
}
399+
400+
#[derive(Clone, Copy)]
401+
struct PrimitiveBenchArgs {
402+
run_length: usize,
403+
valid_percent: usize,
404+
validity_offset: usize,
405+
}
406+
407+
impl fmt::Display for PrimitiveBenchArgs {
408+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409+
write!(
410+
f,
411+
"run_{}_valid_{}_offset_{}",
412+
self.run_length, self.valid_percent, self.validity_offset
413+
)
414+
}
415+
}
416+
417+
const PRIMITIVE_ARGS: [PrimitiveBenchArgs; 30] = {
418+
let mut args = [PrimitiveBenchArgs {
419+
run_length: 1,
420+
valid_percent: 99,
421+
validity_offset: 0,
422+
}; 30];
423+
let lengths = [1, 2, 16, 128, 4096];
424+
let validities = [10, 50, 99];
425+
let mut i = 0;
426+
while i < args.len() {
427+
args[i] = PrimitiveBenchArgs {
428+
run_length: lengths[i / 6],
429+
valid_percent: validities[(i / 2) % 3],
430+
validity_offset: (i % 2) * 3,
431+
};
432+
i += 1;
433+
}
434+
args
435+
};
436+
437+
#[divan::bench(types = [u16, i64], args = PRIMITIVE_ARGS)]
438+
fn decode_primitive_nullable<T: NativePType>(bencher: Bencher, args: PrimitiveBenchArgs) {
439+
let length = 65_536usize;
440+
let run_count = length.div_ceil(args.run_length);
441+
let values: Vec<T> = (0..run_count)
442+
.map(|i| T::from_usize(i % 1024).vortex_expect("benchmark value fits"))
443+
.collect();
444+
let validity = BitBuffer::from_iter(
445+
(0..run_count + args.validity_offset)
446+
.map(|i| (i * 37) % 100 < args.valid_percent),
447+
)
448+
.slice(args.validity_offset..run_count + args.validity_offset);
449+
let validity = Mask::from(validity);
450+
bencher.bench(|| {
451+
runend_decode_typed_primitive(
452+
(1..=run_count).map(|i| (i * args.run_length).min(length)),
453+
&values,
454+
validity.clone(),
455+
Nullability::Nullable,
456+
length,
457+
)
458+
});
459+
}
460+
461+
#[divan::bench(types = [u16, i64], args = [1usize, 2, 16, 128, 4096])]
462+
fn decode_primitive_non_nullable<T: NativePType>(bencher: Bencher, run_length: usize) {
463+
let length = 65_536usize;
464+
let run_count = length.div_ceil(run_length);
465+
let values: Vec<T> = (0..run_count)
466+
.map(|i| T::from_usize(i % 1024).vortex_expect("benchmark value fits"))
467+
.collect();
468+
bencher.bench(|| {
469+
runend_decode_typed_primitive(
470+
(1..=run_count).map(|i| (i * run_length).min(length)),
471+
&values,
472+
Mask::AllTrue(run_count),
473+
Nullability::NonNullable,
474+
length,
475+
)
476+
});
477+
}
478+
479+
#[divan::bench(types = [u16, i64], args = [1usize, 2, 16, 128, 4096])]
480+
fn decode_primitive_array<T: NativePType>(bencher: Bencher, run_length: usize) {
481+
let length = 65_536usize;
482+
let run_count = length.div_ceil(run_length);
483+
let ends = PrimitiveArray::from_iter(
484+
(1..=run_count).map(|i| (i * run_length).min(length) as u32),
485+
);
486+
let values = PrimitiveArray::new(
487+
Buffer::from_iter((0..run_count).map(|i| {
488+
T::from_usize(i % 1024).vortex_expect("benchmark value fits")
489+
})),
490+
Validity::NonNullable,
491+
);
492+
bencher.with_inputs(|| SESSION.create_execution_ctx()).bench_refs(|ctx| {
493+
runend_decode_primitive(ends.clone(), values.clone(), 0, length, ctx)
494+
.vortex_expect("benchmark decode succeeds")
495+
});
496+
}

‎encodings/runend/benches/run_end_filter.rs‎

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@
1414
#![expect(clippy::expect_used)]
1515

1616
use std::fmt;
17+
use std::sync::LazyLock;
1718

1819
use divan::Bencher;
1920
use mimalloc::MiMalloc;
2021
use rand::SeedableRng;
2122
use rand::rngs::StdRng;
2223
use rand::seq::SliceRandom;
24+
use vortex_array::IntoArray;
25+
use vortex_array::VortexSessionExecute;
26+
use vortex_array::arrays::PrimitiveArray;
2327
use vortex_buffer::BitBuffer;
28+
use vortex_mask::Mask;
29+
use vortex_runend::RunEnd;
2430
use vortex_runend::_benchmarking::filter_run_end_primitive;
31+
use vortex_session::VortexSession;
2532

2633
#[global_allocator]
2734
static GLOBAL: MiMalloc = MiMalloc;
@@ -30,6 +37,12 @@ fn main() {
3037
divan::main();
3138
}
3239

40+
static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
41+
let session = vortex_array::array_session();
42+
vortex_runend::initialize(&session);
43+
session
44+
});
45+
3346
#[derive(Clone, Copy)]
3447
struct FilterBenchArgs {
3548
/// Total logical length of the decoded array.
@@ -38,50 +51,41 @@ struct FilterBenchArgs {
3851
run_length: usize,
3952
/// Fraction of mask bits that are set to `true`.
4053
density: f64,
54+
/// Bit offset of a sliced predicate mask.
55+
mask_offset: usize,
4156
}
4257

4358
impl fmt::Display for FilterBenchArgs {
4459
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4560
write!(
4661
f,
47-
"len={}_run={}_density={:.1}",
48-
self.length, self.run_length, self.density
62+
"len={}_run={}_density={:.1}_offset={}",
63+
self.length, self.run_length, self.density, self.mask_offset
4964
)
5065
}
5166
}
5267

53-
const FILTER_ARGS: &[FilterBenchArgs] = &[
54-
FilterBenchArgs {
55-
length: 4_096,
56-
run_length: 16,
57-
density: 0.1,
58-
},
59-
FilterBenchArgs {
60-
length: 4_096,
61-
run_length: 16,
62-
density: 0.5,
63-
},
64-
FilterBenchArgs {
65-
length: 4_096,
66-
run_length: 16,
67-
density: 0.9,
68-
},
69-
FilterBenchArgs {
68+
const FILTER_ARGS: [FilterBenchArgs; 36] = {
69+
let mut args = [FilterBenchArgs {
7070
length: 16_384,
71-
run_length: 16,
71+
run_length: 1,
7272
density: 0.1,
73-
},
74-
FilterBenchArgs {
75-
length: 16_384,
76-
run_length: 16,
77-
density: 0.5,
78-
},
79-
FilterBenchArgs {
80-
length: 16_384,
81-
run_length: 16,
82-
density: 0.9,
83-
},
84-
];
73+
mask_offset: 0,
74+
}; 36];
75+
let lengths = [1, 4, 16, 64, 256, 1024];
76+
let densities = [0.1, 0.5, 0.9];
77+
let mut i = 0;
78+
while i < args.len() {
79+
args[i] = FilterBenchArgs {
80+
length: 16_384,
81+
run_length: lengths[i / 6],
82+
density: densities[(i / 2) % 3],
83+
mask_offset: (i % 2) * 3,
84+
};
85+
i += 1;
86+
}
87+
args
88+
};
8589

8690
/// Build the run-end boundaries (cumulative run lengths) for `length` rows.
8791
fn build_run_ends(length: usize, run_length: usize) -> Vec<u32> {
@@ -107,11 +111,37 @@ fn build_mask(length: usize, density: f64) -> BitBuffer {
107111
#[divan::bench(args = FILTER_ARGS)]
108112
fn filter_run_end(bencher: Bencher, args: FilterBenchArgs) {
109113
let run_ends = build_run_ends(args.length, args.run_length);
110-
let mask = build_mask(args.length, args.density);
114+
let mask = build_mask(args.length + args.mask_offset, args.density)
115+
.slice(args.mask_offset..args.length + args.mask_offset);
111116
let length = args.length as u64;
112117
bencher
113118
.with_inputs(|| (run_ends.clone(), mask.clone()))
114119
.bench_refs(|(run_ends, mask)| {
115120
filter_run_end_primitive::<u32>(run_ends, 0, length, mask).expect("filter")
116121
});
117122
}
123+
124+
#[divan::bench(args = FILTER_ARGS)]
125+
fn filter_run_end_array(bencher: Bencher, args: FilterBenchArgs) {
126+
let ends = build_run_ends(args.length, args.run_length);
127+
let values = PrimitiveArray::from_iter(0..ends.len() as i64).into_array();
128+
let array = RunEnd::new(
129+
PrimitiveArray::from_iter(ends).into_array(),
130+
values,
131+
&mut SESSION.create_execution_ctx(),
132+
)
133+
.into_array();
134+
let mask = Mask::from(
135+
build_mask(args.length + args.mask_offset, args.density)
136+
.slice(args.mask_offset..args.length + args.mask_offset),
137+
);
138+
bencher
139+
.with_inputs(|| (array.clone(), mask.clone(), SESSION.create_execution_ctx()))
140+
.bench_values(|(array, mask, mut ctx)| {
141+
array
142+
.filter(mask)
143+
.expect("filter")
144+
.execute::<PrimitiveArray>(&mut ctx)
145+
.expect("execute")
146+
});
147+
}

0 commit comments

Comments
 (0)