Skip to content

Commit af11cb6

Browse files
authored
Merge branch 'develop' into adamg/minimize-benchmarks
2 parents c92ad93 + 48c33e8 commit af11cb6

16 files changed

Lines changed: 880 additions & 121 deletions

File tree

.github/workflows/run-fuzzer.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ jobs:
116116
- name: Run fuzzing target
117117
id: fuzz
118118
run: |
119+
CORPUS_DIR="fuzz/corpus/${FUZZ_NAME}"
119120
FEATURES_FLAG=""
120121
if [ -n "${{ inputs.extra_features }}" ]; then
121122
FEATURES_FLAG="--features ${{ inputs.extra_features }}"
@@ -127,7 +128,7 @@ jobs:
127128
${{ inputs.extra_env }} RUST_BACKTRACE=1 \
128129
cargo +$NIGHTLY_TOOLCHAIN fuzz run --release --debug-assertions \
129130
$FEATURES_FLAG \
130-
${{ inputs.fuzz_target }} -- \
131+
${{ inputs.fuzz_target }} "$CORPUS_DIR" -- \
131132
$FORK_FLAG -max_total_time=${{ inputs.max_time }} -rss_limit_mb=0 \
132133
2>&1 | tee fuzz_output.log
133134
continue-on-error: true

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fuzz/fuzz_targets/compress_gpu.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
#![no_main]
55
#![expect(clippy::unwrap_used)]
66

7+
use std::sync::LazyLock;
8+
79
use libfuzzer_sys::Corpus;
810
use libfuzzer_sys::fuzz_target;
11+
use tokio::runtime::Builder;
12+
use tokio::runtime::Runtime;
913
use vortex_error::vortex_panic;
1014
use vortex_fuzz::FuzzCompressGpu;
1115
use vortex_fuzz::run_compress_gpu;
1216

13-
fuzz_target!(|fuzz: FuzzCompressGpu| -> Corpus {
14-
// Use tokio runtime to run async GPU fuzzer
15-
let rt = tokio::runtime::Builder::new_current_thread()
16-
.enable_all()
17-
.build()
18-
.unwrap();
17+
static RUNTIME: LazyLock<Runtime> =
18+
LazyLock::new(|| Builder::new_current_thread().enable_all().build().unwrap());
1919

20-
match rt.block_on(run_compress_gpu(fuzz)) {
20+
fuzz_target!(|fuzz: FuzzCompressGpu| -> Corpus {
21+
match RUNTIME.block_on(run_compress_gpu(fuzz)) {
2122
Ok(true) => Corpus::Keep,
2223
Ok(false) => Corpus::Reject,
2324
Err(e) => {

vortex-array/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ harness = false
130130
name = "binary_ops"
131131
harness = false
132132

133+
[[bench]]
134+
name = "kleene_bool"
135+
harness = false
136+
133137
[[bench]]
134138
name = "interleave"
135139
harness = false
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
#![expect(clippy::unwrap_used)]
5+
6+
use std::sync::LazyLock;
7+
8+
use divan::Bencher;
9+
use divan::counter::ItemsCount;
10+
use vortex_array::ArrayRef;
11+
use vortex_array::Columnar;
12+
use vortex_array::IntoArray;
13+
use vortex_array::VortexSessionExecute;
14+
use vortex_array::arrays::BoolArray;
15+
use vortex_array::arrays::ConstantArray;
16+
use vortex_array::arrow::ArrowSession;
17+
use vortex_array::builtins::ArrayBuiltins;
18+
use vortex_array::dtype::DType;
19+
use vortex_array::dtype::Nullability;
20+
use vortex_array::scalar::Scalar;
21+
use vortex_array::scalar_fn::fns::operators::Operator;
22+
use vortex_array::session::ArraySession;
23+
use vortex_session::VortexSession;
24+
25+
fn main() {
26+
divan::main();
27+
}
28+
29+
static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
30+
VortexSession::empty()
31+
.with::<ArraySession>()
32+
.with::<ArrowSession>()
33+
});
34+
35+
const LEN: usize = 65_536;
36+
const SHIFTED_OFFSET: usize = 1;
37+
38+
#[divan::bench]
39+
fn and_bool_nonnull_arrays(bencher: Bencher) {
40+
bench_kleene(
41+
bencher,
42+
bool_nonnull(2).into_array(),
43+
bool_nonnull(3).into_array(),
44+
Operator::And,
45+
);
46+
}
47+
48+
#[divan::bench]
49+
fn or_bool_nonnull_arrays(bencher: Bencher) {
50+
bench_kleene(
51+
bencher,
52+
bool_nonnull(2).into_array(),
53+
bool_nonnull(3).into_array(),
54+
Operator::Or,
55+
);
56+
}
57+
58+
#[divan::bench]
59+
fn and_bool_nullable_arrays(bencher: Bencher) {
60+
and_bool_nullable_arrays_aligned(bencher);
61+
}
62+
63+
#[divan::bench]
64+
fn and_bool_nullable_arrays_aligned(bencher: Bencher) {
65+
bench_kleene(
66+
bencher,
67+
bool_nullable(2, 7, 0),
68+
bool_nullable(3, 5, 0),
69+
Operator::And,
70+
);
71+
}
72+
73+
#[divan::bench]
74+
fn or_bool_nullable_arrays(bencher: Bencher) {
75+
or_bool_nullable_arrays_aligned(bencher);
76+
}
77+
78+
#[divan::bench]
79+
fn or_bool_nullable_arrays_aligned(bencher: Bencher) {
80+
bench_kleene(
81+
bencher,
82+
bool_nullable(2, 7, 0),
83+
bool_nullable(3, 5, 0),
84+
Operator::Or,
85+
);
86+
}
87+
88+
#[divan::bench]
89+
fn and_bool_nullable_arrays_shifted(bencher: Bencher) {
90+
bench_kleene(
91+
bencher,
92+
bool_nullable(2, 7, SHIFTED_OFFSET),
93+
bool_nullable(3, 5, SHIFTED_OFFSET),
94+
Operator::And,
95+
);
96+
}
97+
98+
#[divan::bench]
99+
fn or_bool_nullable_arrays_shifted(bencher: Bencher) {
100+
bench_kleene(
101+
bencher,
102+
bool_nullable(2, 7, SHIFTED_OFFSET),
103+
bool_nullable(3, 5, SHIFTED_OFFSET),
104+
Operator::Or,
105+
);
106+
}
107+
108+
#[divan::bench]
109+
fn and_true_constant(bencher: Bencher) {
110+
bench_kleene(
111+
bencher,
112+
bool_nullable(2, 7, 0),
113+
ConstantArray::new(true, LEN).into_array(),
114+
Operator::And,
115+
);
116+
}
117+
118+
#[divan::bench]
119+
fn or_false_constant(bencher: Bencher) {
120+
bench_kleene(
121+
bencher,
122+
bool_nullable(2, 7, 0),
123+
ConstantArray::new(false, LEN).into_array(),
124+
Operator::Or,
125+
);
126+
}
127+
128+
#[divan::bench]
129+
fn and_false_constant(bencher: Bencher) {
130+
bench_kleene(
131+
bencher,
132+
bool_nullable(2, 7, 0),
133+
ConstantArray::new(false, LEN).into_array(),
134+
Operator::And,
135+
);
136+
}
137+
138+
#[divan::bench]
139+
fn or_true_constant(bencher: Bencher) {
140+
bench_kleene(
141+
bencher,
142+
bool_nullable(2, 7, 0),
143+
ConstantArray::new(true, LEN).into_array(),
144+
Operator::Or,
145+
);
146+
}
147+
148+
#[divan::bench]
149+
fn and_null_constant(bencher: Bencher) {
150+
and_null_constant_aligned(bencher);
151+
}
152+
153+
#[divan::bench]
154+
fn and_null_constant_aligned(bencher: Bencher) {
155+
bench_kleene(
156+
bencher,
157+
bool_nullable(2, 7, 0),
158+
null_bool_constant(),
159+
Operator::And,
160+
);
161+
}
162+
163+
#[divan::bench]
164+
fn or_null_constant(bencher: Bencher) {
165+
or_null_constant_aligned(bencher);
166+
}
167+
168+
#[divan::bench]
169+
fn or_null_constant_aligned(bencher: Bencher) {
170+
bench_kleene(
171+
bencher,
172+
bool_nullable(2, 7, 0),
173+
null_bool_constant(),
174+
Operator::Or,
175+
);
176+
}
177+
178+
#[divan::bench]
179+
fn and_null_constant_shifted(bencher: Bencher) {
180+
bench_kleene(
181+
bencher,
182+
bool_nullable(2, 7, SHIFTED_OFFSET),
183+
null_bool_constant(),
184+
Operator::And,
185+
);
186+
}
187+
188+
#[divan::bench]
189+
fn or_null_constant_shifted(bencher: Bencher) {
190+
bench_kleene(
191+
bencher,
192+
bool_nullable(2, 7, SHIFTED_OFFSET),
193+
null_bool_constant(),
194+
Operator::Or,
195+
);
196+
}
197+
198+
fn bench_kleene(bencher: Bencher, lhs: ArrayRef, rhs: ArrayRef, operator: Operator) {
199+
let mut ctx = SESSION.create_execution_ctx();
200+
201+
bencher.counter(ItemsCount::new(LEN)).bench_local(|| {
202+
lhs.clone()
203+
.binary(rhs.clone(), operator)
204+
.unwrap()
205+
.execute::<Columnar>(&mut ctx)
206+
.unwrap()
207+
});
208+
}
209+
210+
fn bool_nonnull(true_every: usize) -> BoolArray {
211+
BoolArray::from_iter((0..LEN).map(|i| i.is_multiple_of(true_every)))
212+
}
213+
214+
fn bool_nullable(true_every: usize, null_every: usize, offset: usize) -> ArrayRef {
215+
let len = LEN + offset;
216+
let array = BoolArray::from_iter(
217+
(0..len).map(|i| (!i.is_multiple_of(null_every)).then_some(i.is_multiple_of(true_every))),
218+
)
219+
.into_array();
220+
221+
if offset == 0 {
222+
array
223+
} else {
224+
array.slice(offset..offset + LEN).unwrap()
225+
}
226+
}
227+
228+
fn null_bool_constant() -> ArrayRef {
229+
ConstantArray::new(Scalar::null(DType::Bool(Nullability::Nullable)), LEN).into_array()
230+
}

vortex-btrblocks/src/builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub const ALL_SCHEMES: &[&dyn Scheme] = &[
4343
&integer::IntRLEScheme,
4444
// Prefer all other schemes above delta, for now (since its slower to decompress).
4545
#[cfg(feature = "unstable_encodings")]
46-
&integer::DeltaScheme,
46+
&integer::DeltaScheme::new(1.25),
4747
////////////////////////////////////////////////////////////////////////////////////////////////
4848
// Float schemes.
4949
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -186,7 +186,7 @@ impl BtrBlocksCompressorBuilder {
186186
// Delta has no GPU decode kernel and its prefix-sum decode is inherently sequential, so it
187187
// is incompatible with pure-GPU decompression paths.
188188
#[cfg(feature = "unstable_encodings")]
189-
excluded.push(integer::DeltaScheme.id());
189+
excluded.push(integer::DeltaScheme::default().id());
190190
let builder = self.exclude_schemes(excluded);
191191

192192
#[cfg(all(feature = "zstd", feature = "unstable_encodings"))]

vortex-btrblocks/src/schemes/integer/delta.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,29 @@ use crate::SchemeExt;
3434
/// Delta replaces each value with its difference from an earlier value (at the FastLanes lane
3535
/// stride), so a later cascade layer (FoR / BitPacking) packs the smaller residuals. It only
3636
/// pays off when those residuals span meaningfully fewer bits than the values themselves.
37-
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
38-
pub struct DeltaScheme;
37+
///
38+
/// The minimum penalized compression ratio required for Delta to be selected is configurable via
39+
/// [`DeltaScheme::new`]; [`DeltaScheme::default`] uses a ratio of `1.25`.
40+
#[derive(Debug, Copy, Clone, PartialEq)]
41+
pub struct DeltaScheme {
42+
min_ratio: f64,
43+
}
44+
45+
impl DeltaScheme {
46+
/// Creates a Delta scheme requiring `min_ratio` (after the [`DELTA_PENALTY`]) before it wins.
47+
///
48+
/// Pass a higher ratio to make Delta more conservative, or a lower one to select it more
49+
/// eagerly. [`DeltaScheme::default`] uses a ratio of `1.25`.
50+
pub const fn new(min_ratio: f64) -> Self {
51+
Self { min_ratio }
52+
}
53+
}
54+
55+
impl Default for DeltaScheme {
56+
fn default() -> Self {
57+
Self::new(1.25)
58+
}
59+
}
3960

4061
/// Multiplicative penalty applied to Delta's estimated compression ratio.
4162
///
@@ -65,7 +86,7 @@ impl Scheme for DeltaScheme {
6586
/// bases and the deltas children so we never delta-encode data that was already delta-encoded.
6687
fn descendant_exclusions(&self) -> Vec<DescendantExclusion> {
6788
vec![DescendantExclusion {
68-
excluded: DeltaScheme.id(),
89+
excluded: self.id(),
6990
children: ChildSelection::All,
7091
}]
7192
}
@@ -110,8 +131,9 @@ impl Scheme for DeltaScheme {
110131

111132
// Estimating Delta needs the real transposed-delta span, so defer to a callback that
112133
// delta-encodes the array and measures the residual range.
134+
let min_ratio = self.min_ratio;
113135
CompressionEstimate::Deferred(DeferredEstimate::Callback(Box::new(
114-
|_compressor, data, best_so_far, _ctx, exec_ctx| {
136+
move |_compressor, data, best_so_far, _ctx, exec_ctx| {
115137
let primitive = data.array().clone().execute::<PrimitiveArray>(exec_ctx)?;
116138
let full_width = primitive.ptype().bit_width() as f64;
117139

@@ -138,7 +160,7 @@ impl Scheme for DeltaScheme {
138160
};
139161

140162
let ratio = full_width / delta_bits * DELTA_PENALTY;
141-
if ratio <= 1.0 {
163+
if ratio <= min_ratio {
142164
return Ok(EstimateVerdict::Skip);
143165
}
144166
Ok(EstimateVerdict::Ratio(ratio))

0 commit comments

Comments
 (0)