Skip to content

Commit ba00e83

Browse files
authored
Remove ArrayAccessor (#8603)
ArrayAccessor was a poorly thought out api that leaves performance on the table. We never use it in any hot path and only serves as a shortcut for converting string arrays to a vec of strings. There are simpler methods for doing that in tests
1 parent f172d58 commit ba00e83

51 files changed

Lines changed: 754 additions & 831 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

encodings/experimental/onpair/benches/decode.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use vortex_array::ArrayRef;
3333
use vortex_array::ExecutionCtx;
3434
use vortex_array::IntoArray;
3535
use vortex_array::VortexSessionExecute;
36+
use vortex_array::array_session;
3637
use vortex_array::arrays::PrimitiveArray;
3738
use vortex_array::arrays::VarBinArray;
3839
use vortex_array::arrays::VarBinViewArray;
@@ -80,7 +81,7 @@ use vortex_onpair::onpair_compress;
8081
use vortex_session::VortexSession;
8182

8283
static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
83-
let session = vortex_array::array_session();
84+
let session = array_session();
8485
vortex_onpair::initialize(&session);
8586
session
8687
});
@@ -153,13 +154,13 @@ fn corpus(n: usize, shape: Shape) -> Vec<String> {
153154
out
154155
}
155156

156-
fn compress(n: usize, shape: Shape) -> OnPairArray {
157+
fn compress(n: usize, shape: Shape, ctx: &mut ExecutionCtx) -> OnPairArray {
157158
let strings = corpus(n, shape);
158159
let varbin = VarBinArray::from_iter(
159160
strings.iter().map(|s| Some(s.as_bytes())),
160161
DType::Utf8(Nullability::NonNullable),
161162
);
162-
onpair_compress(&varbin, varbin.len(), varbin.dtype(), DEFAULT_DICT12_CONFIG)
163+
onpair_compress(varbin.as_array(), DEFAULT_DICT12_CONFIG, ctx)
163164
.unwrap_or_else(|e| panic!("onpair_compress failed: {e}"))
164165
}
165166

@@ -172,13 +173,12 @@ fn widen<T: NativePType>(arr: &ArrayRef, ctx: &mut ExecutionCtx) -> Buffer<T> {
172173
.into_buffer::<T>()
173174
}
174175

175-
fn materialise(arr: &OnPairArray) -> (DecodeInputs, usize) {
176-
let mut ctx = SESSION.create_execution_ctx();
176+
fn materialise(arr: &OnPairArray, ctx: &mut ExecutionCtx) -> (DecodeInputs, usize) {
177177
let view = arr.as_view();
178178
let inputs = DecodeInputs {
179179
dict_bytes: view.dict_bytes().clone(),
180-
dict_offsets: widen::<u32>(view.dict_offsets(), &mut ctx),
181-
codes: widen::<u16>(view.codes(), &mut ctx),
180+
dict_offsets: widen::<u32>(view.dict_offsets(), ctx),
181+
codes: widen::<u16>(view.codes(), ctx),
182182
bits: view.bits(),
183183
};
184184
let total = inputs.decompressed_len();
@@ -197,9 +197,10 @@ const CASES: &[(Shape, usize)] = &[
197197
/// Hits `onpair::decompress_into` directly.
198198
#[divan::bench(args = CASES)]
199199
fn decompress_into_bench(bencher: Bencher, case: (Shape, usize)) {
200+
let mut ctx = SESSION.create_execution_ctx();
200201
let (shape, n) = case;
201-
let arr = compress(n, shape);
202-
let (inputs, total) = materialise(&arr);
202+
let arr = compress(n, shape, &mut ctx);
203+
let (inputs, total) = materialise(&arr, &mut ctx);
203204
bencher.bench_local(|| {
204205
let mut out: Vec<u8> = Vec::with_capacity(total);
205206
let written = inputs.decompress_into(out.spare_capacity_mut());
@@ -212,8 +213,9 @@ fn decompress_into_bench(bencher: Bencher, case: (Shape, usize)) {
212213
/// building the view buffer + `BinaryView` list, etc.
213214
#[divan::bench(args = CASES)]
214215
fn canonicalize_to_varbinview(bencher: Bencher, case: (Shape, usize)) {
216+
let mut ctx = SESSION.create_execution_ctx();
215217
let (shape, n) = case;
216-
let arr = compress(n, shape);
218+
let arr = compress(n, shape, &mut ctx);
217219
bencher
218220
.with_inputs(|| (arr.clone().into_array(), SESSION.create_execution_ctx()))
219221
.bench_local_values(|(arr, mut ctx)| {
@@ -232,8 +234,9 @@ const COMPUTE_CASES: &[(Shape, usize)] = &[(Shape::UrlLog, 100_000), (Shape::Url
232234
/// rows; the cost is dominated by the `codes` segment copy + offsets.
233235
#[divan::bench(args = COMPUTE_CASES)]
234236
fn filter_share_dict(bencher: Bencher, case: (Shape, usize)) {
237+
let mut ctx = SESSION.create_execution_ctx();
235238
let (shape, n) = case;
236-
let arr = compress(n, shape);
239+
let arr = compress(n, shape, &mut ctx);
237240
let mask = Mask::from_iter((0..n).map(|i| i % 7 == 0));
238241
bencher
239242
.with_inputs(|| SESSION.create_execution_ctx())

encodings/experimental/onpair/src/compress.rs

Lines changed: 80 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,70 +8,87 @@ use onpair::Offset;
88
use vortex_array::ArrayRef;
99
use vortex_array::ExecutionCtx;
1010
use vortex_array::IntoArray;
11-
use vortex_array::LEGACY_SESSION;
12-
use vortex_array::VortexSessionExecute;
13-
use vortex_array::accessor::ArrayAccessor;
11+
use vortex_array::arrays::ConstantArray;
1412
use vortex_array::arrays::VarBinViewArray;
13+
use vortex_array::arrays::varbinview::BinaryView;
1514
use vortex_array::buffer::BufferHandle;
16-
use vortex_array::dtype::DType;
17-
use vortex_array::dtype::Nullability;
1815
use vortex_array::validity::Validity;
16+
use vortex_buffer::Alignment;
1917
use vortex_buffer::Buffer;
2018
use vortex_buffer::BufferMut;
2119
use vortex_buffer::ByteBuffer;
20+
use vortex_buffer::ByteBufferMut;
2221
use vortex_error::VortexExpect;
2322
use vortex_error::VortexResult;
2423
use vortex_error::vortex_err;
24+
use vortex_mask::AllOr;
2525

2626
use crate::OnPair;
2727
use crate::OnPairArray;
2828

2929
/// Default OnPair training configuration: 12-bit codes ("dict-12").
3030
pub const DEFAULT_DICT12_CONFIG: Config = onpair::DEFAULT_CONFIG;
3131

32-
/// Compress an iterable of optional byte strings via the OnPair encoder.
33-
pub fn onpair_compress_iter<'a, I>(
34-
iter: I,
35-
len: usize,
36-
dtype: DType,
37-
config: Config,
38-
) -> VortexResult<OnPairArray>
39-
where
40-
I: Iterator<Item = Option<&'a [u8]>>,
41-
{
42-
onpair_compress_iter_with_offsets::<u64, _>(iter, len, dtype, config)
43-
}
44-
45-
fn onpair_compress_iter_with_offsets<'a, O, I>(
46-
iter: I,
47-
len: usize,
48-
dtype: DType,
32+
fn onpair_compress_varbinview<O>(
33+
array: VarBinViewArray,
4934
config: Config,
35+
ctx: &mut ExecutionCtx,
5036
) -> VortexResult<OnPairArray>
5137
where
5238
O: Offset,
53-
I: Iterator<Item = Option<&'a [u8]>>,
5439
{
40+
let len = array.len();
41+
let mask = array.validity()?.execute_mask(len, ctx)?;
42+
if mask.all_false() {
43+
return OnPair::try_new(
44+
array.dtype().clone(),
45+
BufferHandle::new_host(ByteBuffer::empty()),
46+
ConstantArray::new(0, len).into_array(),
47+
ConstantArray::new(0u16, len).into_array(),
48+
ConstantArray::new(0u32, len + 1).into_array(),
49+
ConstantArray::new(0i32, len).into_array(),
50+
Validity::AllInvalid,
51+
9,
52+
);
53+
}
54+
5555
let mut flat: Vec<u8> = Vec::with_capacity(len * 16);
5656
let mut offsets: Vec<O> = Vec::with_capacity(len + 1);
5757
let mut uncompressed_lengths: BufferMut<i32> = BufferMut::with_capacity(len);
58-
let mut validity_bits: Vec<bool> = Vec::with_capacity(len);
59-
offsets.push(<O as Offset>::from_usize(0));
58+
offsets.push(O::from_usize(0));
59+
let views = array.views();
60+
let buffers = array
61+
.data_buffers()
62+
.as_ref()
63+
.iter()
64+
.map(|b| b.as_host())
65+
.collect::<Vec<_>>();
6066

61-
for item in iter {
62-
match item {
63-
Some(bytes) => {
67+
match mask.bit_buffer() {
68+
AllOr::All => {
69+
for view in views {
70+
let bytes = view_bytes(view, &buffers);
6471
flat.extend_from_slice(bytes);
65-
offsets.push(<O as Offset>::from_usize(flat.len()));
66-
uncompressed_lengths.push(
67-
i32::try_from(bytes.len()).vortex_expect("string length must fit in i32"),
68-
);
69-
validity_bits.push(true);
72+
offsets.push(O::from_usize(flat.len()));
73+
uncompressed_lengths
74+
.push(i32::try_from(view.len()).vortex_expect("must fit in i32"));
7075
}
71-
None => {
72-
offsets.push(<O as Offset>::from_usize(flat.len()));
73-
uncompressed_lengths.push(0);
74-
validity_bits.push(false);
76+
}
77+
AllOr::None => {
78+
unreachable!("all_false() should have been caught earlier");
79+
}
80+
AllOr::Some(validity) => {
81+
for (view, valid) in views.iter().zip(validity.iter()) {
82+
if valid {
83+
let bytes = view_bytes(view, &buffers);
84+
flat.extend_from_slice(bytes);
85+
offsets.push(O::from_usize(flat.len()));
86+
uncompressed_lengths
87+
.push(i32::try_from(view.len()).vortex_expect("must fit in i32"));
88+
} else {
89+
offsets.push(O::from_usize(flat.len()));
90+
uncompressed_lengths.push(0);
91+
}
7592
}
7693
}
7794
}
@@ -80,44 +97,53 @@ where
8097
.map_err(|e| vortex_err!("OnPair compress failed: {e}"))?;
8198
let bits = column.bits;
8299
let dict_bytes = dict_bytes_to_buffer(column.dict_bytes);
83-
let codes_offsets = build_codes_offsets(&column.codes, &column.dict_offsets, &offsets)?;
100+
let codes_offsets =
101+
build_codes_offsets(&column.codes, &column.dict_offsets, &offsets)?.into_array();
84102
let codes = Buffer::from(column.codes).into_array();
85103
let dict_offsets = Buffer::from(column.dict_offsets).into_array();
86-
let codes_offsets = Buffer::from(codes_offsets).into_array();
87104

88105
let uncompressed_lengths = uncompressed_lengths.into_array();
89-
let validity = match dtype.nullability() {
90-
Nullability::NonNullable => Validity::NonNullable,
91-
Nullability::Nullable => Validity::from_iter(validity_bits),
92-
};
93106

94107
OnPair::try_new(
95-
dtype,
108+
array.dtype().clone(),
96109
dict_bytes,
97110
dict_offsets,
98111
codes,
99112
codes_offsets,
100113
uncompressed_lengths,
101-
validity,
114+
array.validity()?,
102115
bits,
103116
)
104117
}
105118

119+
fn view_bytes<'a>(view: &'a BinaryView, buffers: &'a [&ByteBuffer]) -> &'a [u8] {
120+
if view.is_inlined() {
121+
view.as_inlined().value()
122+
} else {
123+
let view_ref = view.as_view();
124+
&buffers[view_ref.buffer_index as usize][view_ref.as_range()]
125+
}
126+
}
127+
106128
/// Lift compressed dictionary bytes into the Vortex buffer slot.
107129
fn dict_bytes_to_buffer(dict_bytes: Vec<u8>) -> BufferHandle {
108130
// Pad the dictionary blob with MAX_TOKEN_SIZE zero bytes so the
109131
// over-copy decoder can issue a fixed 16-byte load for every token
110132
// without risking an OOB read on the last entry.
111-
let mut padded = Vec::with_capacity(dict_bytes.len() + onpair::MAX_TOKEN_SIZE);
112-
padded.extend_from_slice(&dict_bytes);
113-
padded.resize(dict_bytes.len() + onpair::MAX_TOKEN_SIZE, 0);
133+
//
114134
// Align dict_bytes to 8 bytes so the segment that ultimately holds the
115135
// OnPair tree starts at an 8-aligned in-memory address. Without this
116136
// anchor, the per-buffer padding the serializer inserts is only
117137
// *relative* to the segment start; if the segment lands at a u8-aligned
118138
// heap address, downstream `PrimitiveArray<u32>::deserialize` panics
119139
// with `Misaligned buffer cannot be used to build PrimitiveArray of u32`.
120-
BufferHandle::new_host(ByteBuffer::from(padded).aligned(vortex_buffer::Alignment::new(8)))
140+
let mut padded = ByteBufferMut::with_capacity_aligned(
141+
dict_bytes.len() + onpair::MAX_TOKEN_SIZE,
142+
Alignment::new(8),
143+
);
144+
padded.extend_from_slice(&dict_bytes);
145+
unsafe { padded.push_n_unchecked(0, dict_bytes.len() + onpair::MAX_TOKEN_SIZE - padded.len()) };
146+
BufferHandle::new_host(padded.freeze())
121147
}
122148

123149
/// Reconstruct the per-row `codes_offsets` from the flat `codes`, the
@@ -128,9 +154,9 @@ fn build_codes_offsets<O: Offset>(
128154
codes: &[u16],
129155
dict_offsets: &[u32],
130156
row_byte_offsets: &[O],
131-
) -> VortexResult<Vec<u32>> {
157+
) -> VortexResult<Buffer<u32>> {
132158
let nrows = row_byte_offsets.len() - 1;
133-
let mut codes_offsets = Vec::with_capacity(nrows + 1);
159+
let mut codes_offsets = BufferMut::with_capacity(nrows + 1);
134160
codes_offsets.push(0u32);
135161
let mut decoded_bytes: u64 = 0;
136162
let mut code_idx: usize = 0;
@@ -149,38 +175,16 @@ fn build_codes_offsets<O: Offset>(
149175
.map_err(|_| vortex_err!("OnPair: code boundary {code_idx} does not fit u32"))?,
150176
);
151177
}
152-
Ok(codes_offsets)
153-
}
154-
155-
/// Compress a byte-string accessor (typically a `VarBinArray` or
156-
/// `VarBinViewArray`).
157-
pub fn onpair_compress<A: ArrayAccessor<[u8]>>(
158-
array: A,
159-
len: usize,
160-
dtype: &DType,
161-
config: Config,
162-
) -> VortexResult<OnPairArray> {
163-
array.with_iterator(|iter| onpair_compress_iter(iter, len, dtype.clone(), config))
178+
Ok(codes_offsets.freeze())
164179
}
165180

166181
/// Compress any [`ArrayRef`] whose canonical form is a string array, by first
167182
/// canonicalising to `VarBinViewArray`.
168-
pub fn onpair_compress_array(
183+
pub fn onpair_compress(
169184
array: &ArrayRef,
170185
config: Config,
171186
ctx: &mut ExecutionCtx,
172187
) -> VortexResult<OnPairArray> {
173188
let view = array.clone().execute::<VarBinViewArray>(ctx)?;
174-
let len = view.len();
175-
let dtype = view.dtype().clone();
176-
onpair_compress(&view, len, &dtype, config)
177-
}
178-
179-
/// Convenience: build a default `ExecutionCtx` from `LEGACY_SESSION`.
180-
pub fn onpair_compress_array_default(
181-
array: &ArrayRef,
182-
config: Config,
183-
) -> VortexResult<OnPairArray> {
184-
let mut ctx = LEGACY_SESSION.create_execution_ctx();
185-
onpair_compress_array(array, config, &mut ctx)
189+
onpair_compress_varbinview::<u64>(view, config, ctx)
186190
}

encodings/experimental/onpair/src/compute/compare.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mod tests {
6969
use rstest::rstest;
7070
use vortex_array::IntoArray;
7171
use vortex_array::VortexSessionExecute;
72+
use vortex_array::array_session;
7273
use vortex_array::arrays::BoolArray;
7374
use vortex_array::arrays::ConstantArray;
7475
use vortex_array::arrays::VarBinArray;
@@ -84,7 +85,7 @@ mod tests {
8485
use crate::compress::DEFAULT_DICT12_CONFIG;
8586
use crate::compress::onpair_compress;
8687

87-
static SESSION: LazyLock<VortexSession> = LazyLock::new(vortex_array::array_session);
88+
static SESSION: LazyLock<VortexSession> = LazyLock::new(array_session);
8889

8990
#[cfg_attr(miri, ignore)]
9091
#[rstest]
@@ -99,10 +100,9 @@ mod tests {
99100
[Some(""), Some("a"), Some(""), Some("bbb")],
100101
DType::Utf8(Nullability::NonNullable),
101102
);
102-
let arr = onpair_compress(&input, input.len(), input.dtype(), DEFAULT_DICT12_CONFIG)?
103-
.into_array();
104-
105103
let mut ctx = SESSION.create_execution_ctx();
104+
let arr = onpair_compress(input.as_array(), DEFAULT_DICT12_CONFIG, &mut ctx)?.into_array();
105+
106106
let result = arr
107107
.binary(ConstantArray::new("", input.len()).into_array(), op)?
108108
.execute::<BoolArray>(&mut ctx)?;
@@ -117,9 +117,8 @@ mod tests {
117117
[Some(""), None, Some("x")],
118118
DType::Utf8(Nullability::Nullable),
119119
);
120-
let arr = onpair_compress(&input, input.len(), input.dtype(), DEFAULT_DICT12_CONFIG)?
121-
.into_array();
122120
let mut ctx = SESSION.create_execution_ctx();
121+
let arr = onpair_compress(input.as_array(), DEFAULT_DICT12_CONFIG, &mut ctx)?.into_array();
123122

124123
let eq_empty = arr
125124
.clone()

0 commit comments

Comments
 (0)