1use arrow_array::types::UInt64Type;
19use arrow_array::{Array, PrimitiveArray};
20use arrow_buffer::ArrowNativeType;
21use byteorder::{ByteOrder, LittleEndian};
22use lance_bitpacking::BitPacking;
23
24use lance_core::{Error, Result};
25
26use crate::buffer::LanceBuffer;
27use crate::compression::{BlockCompressor, BlockDecompressor, MiniBlockDecompressor};
28use crate::data::BlockInfo;
29use crate::data::{DataBlock, FixedWidthDataBlock};
30use crate::encodings::logical::primitive::miniblock::{
31 MiniBlockChunk, MiniBlockCompressed, MiniBlockCompressor,
32};
33use crate::format::pb21::CompressiveEncoding;
34use crate::format::{ProtobufUtils21, pb21};
35use crate::statistics::{GetStat, Stat};
36use bytemuck::{AnyBitPattern, cast_slice};
37
38const LOG_ELEMS_PER_CHUNK: u8 = 10;
39const ELEMS_PER_CHUNK: u64 = 1 << LOG_ELEMS_PER_CHUNK;
40
41#[derive(Debug, Default)]
42pub struct InlineBitpacking {
43 uncompressed_bit_width: u64,
44}
45
46impl InlineBitpacking {
47 pub fn new(uncompressed_bit_width: u64) -> Self {
48 Self {
49 uncompressed_bit_width,
50 }
51 }
52
53 pub fn from_description(description: &pb21::InlineBitpacking) -> Self {
54 Self {
55 uncompressed_bit_width: description.uncompressed_bits_per_value,
56 }
57 }
58
59 pub fn min_size_bytes(compressed_bit_width: u64) -> u64 {
65 (ELEMS_PER_CHUNK * compressed_bit_width).div_ceil(8)
66 }
67
68 fn bitpack_chunked<T: ArrowNativeType + BitPacking>(
74 data: FixedWidthDataBlock,
75 ) -> MiniBlockCompressed {
76 debug_assert!(data.num_values > 0);
77 let data_buffer = data.data.borrow_to_typed_slice::<T>();
78 let data_buffer = data_buffer.as_ref();
79
80 let bit_widths = data.expect_stat(Stat::BitWidth);
81 let bit_widths_array = bit_widths
82 .as_any()
83 .downcast_ref::<PrimitiveArray<UInt64Type>>()
84 .unwrap();
85
86 let (packed_chunk_sizes, total_size) = bit_widths_array
87 .values()
88 .iter()
89 .map(|&bit_width| {
90 let chunk_size = ((1024 * bit_width) / data.bits_per_value) as usize;
91 (chunk_size, chunk_size + 1)
92 })
93 .fold(
94 (Vec::with_capacity(bit_widths_array.len()), 0),
95 |(mut sizes, total), (size, inc)| {
96 sizes.push(size);
97 (sizes, total + inc)
98 },
99 );
100
101 let mut output: Vec<T> = Vec::with_capacity(total_size);
102 let mut chunks = Vec::with_capacity(bit_widths_array.len());
103
104 for (i, packed_chunk_size) in packed_chunk_sizes
105 .iter()
106 .enumerate()
107 .take(bit_widths_array.len() - 1)
108 {
109 let start_elem = i * ELEMS_PER_CHUNK as usize;
110 let bit_width = bit_widths_array.value(i) as usize;
111 output.push(T::from_usize(bit_width).unwrap());
112 let output_len = output.len();
113 unsafe {
114 output.set_len(output_len + *packed_chunk_size);
115 BitPacking::unchecked_pack(
116 bit_width,
117 &data_buffer[start_elem..][..ELEMS_PER_CHUNK as usize],
118 &mut output[output_len..][..*packed_chunk_size],
119 );
120 }
121 chunks.push(MiniBlockChunk {
122 buffer_sizes: vec![((1 + *packed_chunk_size) * std::mem::size_of::<T>()) as u32],
123 log_num_values: LOG_ELEMS_PER_CHUNK,
124 });
125 }
126
127 let last_chunk_elem_num = if data.num_values.is_multiple_of(ELEMS_PER_CHUNK) {
129 ELEMS_PER_CHUNK
130 } else {
131 data.num_values % ELEMS_PER_CHUNK
132 };
133 let mut last_chunk: Vec<T> = vec![T::from_usize(0).unwrap(); ELEMS_PER_CHUNK as usize];
134 last_chunk[..last_chunk_elem_num as usize].clone_from_slice(
135 &data_buffer[data.num_values as usize - last_chunk_elem_num as usize..],
136 );
137 let bit_width = bit_widths_array.value(bit_widths_array.len() - 1) as usize;
138 output.push(T::from_usize(bit_width).unwrap());
139 let output_len = output.len();
140 unsafe {
141 output.set_len(output_len + packed_chunk_sizes[bit_widths_array.len() - 1]);
142 BitPacking::unchecked_pack(
143 bit_width,
144 &last_chunk,
145 &mut output[output_len..][..packed_chunk_sizes[bit_widths_array.len() - 1]],
146 );
147 }
148 chunks.push(MiniBlockChunk {
149 buffer_sizes: vec![
150 ((1 + packed_chunk_sizes[bit_widths_array.len() - 1]) * std::mem::size_of::<T>())
151 as u32,
152 ],
153 log_num_values: 0,
154 });
155
156 MiniBlockCompressed {
157 data: vec![LanceBuffer::reinterpret_vec(output)],
158 chunks,
159 num_values: data.num_values,
160 }
161 }
162
163 fn chunk_data(&self, data: FixedWidthDataBlock) -> (MiniBlockCompressed, CompressiveEncoding) {
164 assert!(data.bits_per_value.is_multiple_of(8));
165 assert_eq!(data.bits_per_value, self.uncompressed_bit_width);
166 let bits_per_value = data.bits_per_value;
167 let compressed = match bits_per_value {
168 8 => Self::bitpack_chunked::<u8>(data),
169 16 => Self::bitpack_chunked::<u16>(data),
170 32 => Self::bitpack_chunked::<u32>(data),
171 64 => Self::bitpack_chunked::<u64>(data),
172 _ => unreachable!(),
173 };
174 (
175 compressed,
176 ProtobufUtils21::inline_bitpacking(
177 bits_per_value,
178 None,
180 ),
181 )
182 }
183
184 fn unchunk<T: ArrowNativeType + BitPacking + AnyBitPattern>(
185 data: LanceBuffer,
186 num_values: u64,
187 ) -> Result<DataBlock> {
188 assert!(data.len() >= std::mem::size_of::<T>());
190 assert!(num_values <= ELEMS_PER_CHUNK);
191
192 let uncompressed_bit_width = std::mem::size_of::<T>() * 8;
194 let mut decompressed = vec![T::from_usize(0).unwrap(); ELEMS_PER_CHUNK as usize];
195
196 let chunk_in_u8: Vec<u8> = data.to_vec();
198 let bit_width_bytes = &chunk_in_u8[..std::mem::size_of::<T>()];
199 let bit_width_value = LittleEndian::read_uint(bit_width_bytes, std::mem::size_of::<T>());
200 let chunk = cast_slice(&chunk_in_u8[std::mem::size_of::<T>()..]);
201 assert!(std::mem::size_of_val(chunk) == (bit_width_value * ELEMS_PER_CHUNK) as usize / 8);
203 unsafe {
204 BitPacking::unchecked_unpack(bit_width_value as usize, chunk, &mut decompressed);
205 }
206
207 decompressed.truncate(num_values as usize);
208 Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
209 data: LanceBuffer::reinterpret_vec(decompressed),
210 bits_per_value: uncompressed_bit_width as u64,
211 num_values,
212 block_info: BlockInfo::new(),
213 }))
214 }
215}
216
217impl MiniBlockCompressor for InlineBitpacking {
218 fn compress(&self, chunk: DataBlock) -> Result<(MiniBlockCompressed, CompressiveEncoding)> {
219 match chunk {
220 DataBlock::FixedWidth(fixed_width) => Ok(self.chunk_data(fixed_width)),
221 _ => Err(Error::invalid_input_source(
222 format!(
223 "Cannot compress a data block of type {} with BitpackMiniBlockEncoder",
224 chunk.name()
225 )
226 .into(),
227 )),
228 }
229 }
230}
231
232impl BlockCompressor for InlineBitpacking {
233 fn compress(&self, data: DataBlock) -> Result<LanceBuffer> {
234 let fixed_width = data.as_fixed_width().unwrap();
235 let (chunked, _) = self.chunk_data(fixed_width);
236 Ok(chunked.data.into_iter().next().unwrap())
237 }
238}
239
240impl MiniBlockDecompressor for InlineBitpacking {
241 fn decompress(&self, data: Vec<LanceBuffer>, num_values: u64) -> Result<DataBlock> {
242 assert_eq!(data.len(), 1);
243 let data = data.into_iter().next().unwrap();
244 if num_values == 0 {
245 return Ok(DataBlock::FixedWidth(FixedWidthDataBlock {
247 data: LanceBuffer::empty(),
248 bits_per_value: self.uncompressed_bit_width,
249 num_values: 0,
250 block_info: BlockInfo::new(),
251 }));
252 }
253 match self.uncompressed_bit_width {
254 8 => Self::unchunk::<u8>(data, num_values),
255 16 => Self::unchunk::<u16>(data, num_values),
256 32 => Self::unchunk::<u32>(data, num_values),
257 64 => Self::unchunk::<u64>(data, num_values),
258 _ => unimplemented!("Bitpacking word size must be 8, 16, 32, or 64"),
259 }
260 }
261}
262
263impl BlockDecompressor for InlineBitpacking {
264 fn decompress(&self, data: LanceBuffer, num_values: u64) -> Result<DataBlock> {
265 match self.uncompressed_bit_width {
266 8 => Self::unchunk::<u8>(data, num_values),
267 16 => Self::unchunk::<u16>(data, num_values),
268 32 => Self::unchunk::<u32>(data, num_values),
269 64 => Self::unchunk::<u64>(data, num_values),
270 _ => unimplemented!("Bitpacking word size must be 8, 16, 32, or 64"),
271 }
272 }
273}
274
275fn bitpack_out_of_line<T: ArrowNativeType + BitPacking>(
281 data: FixedWidthDataBlock,
282 compressed_bits_per_value: usize,
283) -> LanceBuffer {
284 let data_buffer = data.data.borrow_to_typed_slice::<T>();
285 let data_buffer = data_buffer.as_ref();
286
287 let num_chunks = data_buffer.len().div_ceil(ELEMS_PER_CHUNK as usize);
288 let last_chunk_is_runt = data_buffer.len() % ELEMS_PER_CHUNK as usize != 0;
289 let words_per_chunk = (ELEMS_PER_CHUNK as usize * compressed_bits_per_value)
290 .div_ceil(data.bits_per_value as usize);
291 #[allow(clippy::uninit_vec)]
292 let mut output: Vec<T> = Vec::with_capacity(num_chunks * words_per_chunk);
293 #[allow(clippy::uninit_vec)]
294 unsafe {
295 output.set_len(num_chunks * words_per_chunk);
296 }
297
298 let num_whole_chunks = if last_chunk_is_runt {
299 num_chunks - 1
300 } else {
301 num_chunks
302 };
303
304 for i in 0..num_whole_chunks {
306 let input_start = i * ELEMS_PER_CHUNK as usize;
307 let input_end = input_start + ELEMS_PER_CHUNK as usize;
308 let output_start = i * words_per_chunk;
309 let output_end = output_start + words_per_chunk;
310 unsafe {
311 BitPacking::unchecked_pack(
312 compressed_bits_per_value,
313 &data_buffer[input_start..input_end],
314 &mut output[output_start..output_end],
315 );
316 }
317 }
318
319 if !last_chunk_is_runt {
320 return LanceBuffer::reinterpret_vec(output);
321 }
322
323 let last_chunk_start = num_whole_chunks * ELEMS_PER_CHUNK as usize;
324 unsafe {
326 output.set_len(num_whole_chunks * words_per_chunk);
327 }
328 let remaining_items = data_buffer.len() - last_chunk_start;
329
330 let uncompressed_bits = data.bits_per_value as usize;
331 let tail_bit_savings = uncompressed_bits.saturating_sub(compressed_bits_per_value);
332 let padding_cost = compressed_bits_per_value * (ELEMS_PER_CHUNK as usize - remaining_items);
333 let tail_pack_savings = tail_bit_savings.saturating_mul(remaining_items);
334 debug_assert!(remaining_items > 0, "remaining_items must be non-zero");
335 debug_assert!(tail_bit_savings > 0, "tail_bit_savings must be non-zero");
336
337 if padding_cost < tail_pack_savings {
338 let mut last_chunk: Vec<T> = vec![T::from_usize(0).unwrap(); ELEMS_PER_CHUNK as usize];
340 last_chunk[..remaining_items].copy_from_slice(&data_buffer[last_chunk_start..]);
341 let start = output.len();
342 unsafe {
343 output.set_len(start + words_per_chunk);
345 BitPacking::unchecked_pack(
346 compressed_bits_per_value,
347 &last_chunk,
348 &mut output[start..start + words_per_chunk],
349 );
350 }
351 } else {
352 output.extend_from_slice(&data_buffer[last_chunk_start..]);
354 }
355
356 LanceBuffer::reinterpret_vec(output)
357}
358
359fn unpack_out_of_line<T: ArrowNativeType + BitPacking>(
365 data: FixedWidthDataBlock,
366 num_values: usize,
367 compressed_bits_per_value: usize,
368) -> FixedWidthDataBlock {
369 let words_per_chunk = (ELEMS_PER_CHUNK as usize * compressed_bits_per_value)
370 .div_ceil(data.bits_per_value as usize);
371 let compressed_words = data.data.borrow_to_typed_slice::<T>();
372
373 let num_whole_chunks = num_values / ELEMS_PER_CHUNK as usize;
374 let tail_values = num_values % ELEMS_PER_CHUNK as usize;
375 let expected_full_words = num_whole_chunks * words_per_chunk;
376 let expected_new_len = expected_full_words + tail_values;
377 let tail_is_raw = tail_values > 0 && compressed_words.len() == expected_new_len;
378
379 let extra_tail_capacity = ELEMS_PER_CHUNK as usize;
380 #[allow(clippy::uninit_vec)]
381 let mut decompressed: Vec<T> =
382 Vec::with_capacity(num_values.saturating_add(extra_tail_capacity));
383 let chunk_value_len = num_whole_chunks * ELEMS_PER_CHUNK as usize;
384 unsafe {
385 decompressed.set_len(chunk_value_len);
386 }
387
388 for chunk_idx in 0..num_whole_chunks {
389 let input_start = chunk_idx * words_per_chunk;
390 let input_end = input_start + words_per_chunk;
391 let output_start = chunk_idx * ELEMS_PER_CHUNK as usize;
392 let output_end = output_start + ELEMS_PER_CHUNK as usize;
393 unsafe {
394 BitPacking::unchecked_unpack(
395 compressed_bits_per_value,
396 &compressed_words[input_start..input_end],
397 &mut decompressed[output_start..output_end],
398 );
399 }
400 }
401
402 if tail_values > 0 {
403 if tail_is_raw {
406 let tail_start = expected_full_words;
407 decompressed.extend_from_slice(&compressed_words[tail_start..tail_start + tail_values]);
408 } else {
409 let tail_start = expected_full_words;
410 let output_start = decompressed.len();
411 unsafe {
412 decompressed.set_len(output_start + ELEMS_PER_CHUNK as usize);
413 }
414 unsafe {
415 BitPacking::unchecked_unpack(
416 compressed_bits_per_value,
417 &compressed_words[tail_start..tail_start + words_per_chunk],
418 &mut decompressed[output_start..output_start + ELEMS_PER_CHUNK as usize],
419 );
420 }
421 decompressed.truncate(output_start + tail_values);
422 }
423 }
424
425 debug_assert_eq!(decompressed.len(), num_values);
426
427 FixedWidthDataBlock {
428 data: LanceBuffer::reinterpret_vec(decompressed),
429 bits_per_value: data.bits_per_value,
430 num_values: num_values as u64,
431 block_info: BlockInfo::new(),
432 }
433}
434
435#[derive(Debug)]
461pub struct OutOfLineBitpacking {
462 compressed_bit_width: u64,
463 uncompressed_bit_width: u64,
464}
465
466impl OutOfLineBitpacking {
467 pub fn new(compressed_bit_width: u64, uncompressed_bit_width: u64) -> Self {
468 Self {
469 compressed_bit_width,
470 uncompressed_bit_width,
471 }
472 }
473}
474
475impl BlockCompressor for OutOfLineBitpacking {
476 fn compress(&self, data: DataBlock) -> Result<LanceBuffer> {
477 let fixed_width = data.as_fixed_width().unwrap();
478 let compressed = match fixed_width.bits_per_value {
479 8 => bitpack_out_of_line::<u8>(fixed_width, self.compressed_bit_width as usize),
480 16 => bitpack_out_of_line::<u16>(fixed_width, self.compressed_bit_width as usize),
481 32 => bitpack_out_of_line::<u32>(fixed_width, self.compressed_bit_width as usize),
482 64 => bitpack_out_of_line::<u64>(fixed_width, self.compressed_bit_width as usize),
483 _ => panic!("Bitpacking word size must be 8,16,32,64"),
484 };
485 Ok(compressed)
486 }
487}
488
489impl BlockDecompressor for OutOfLineBitpacking {
490 fn decompress(&self, data: LanceBuffer, num_values: u64) -> Result<DataBlock> {
491 let word_size = match self.uncompressed_bit_width {
492 8 => std::mem::size_of::<u8>(),
493 16 => std::mem::size_of::<u16>(),
494 32 => std::mem::size_of::<u32>(),
495 64 => std::mem::size_of::<u64>(),
496 _ => panic!("Bitpacking word size must be 8,16,32,64"),
497 };
498 debug_assert_eq!(data.len() % word_size, 0);
499 let total_words = (data.len() / word_size) as u64;
500 let block = FixedWidthDataBlock {
501 data,
502 bits_per_value: self.uncompressed_bit_width,
503 num_values: total_words,
504 block_info: BlockInfo::new(),
505 };
506
507 let unpacked = match self.uncompressed_bit_width {
508 8 => unpack_out_of_line::<u8>(
509 block,
510 num_values as usize,
511 self.compressed_bit_width as usize,
512 ),
513 16 => unpack_out_of_line::<u16>(
514 block,
515 num_values as usize,
516 self.compressed_bit_width as usize,
517 ),
518 32 => unpack_out_of_line::<u32>(
519 block,
520 num_values as usize,
521 self.compressed_bit_width as usize,
522 ),
523 64 => unpack_out_of_line::<u64>(
524 block,
525 num_values as usize,
526 self.compressed_bit_width as usize,
527 ),
528 _ => unreachable!(),
529 };
530 Ok(DataBlock::FixedWidth(unpacked))
531 }
532}
533
534#[cfg(test)]
535mod test {
536 use std::{collections::HashMap, sync::Arc};
537
538 use arrow_array::{Array, Int8Array, Int64Array};
539 use arrow_schema::DataType;
540 use rstest::rstest;
541
542 use super::{ELEMS_PER_CHUNK, InlineBitpacking, bitpack_out_of_line, unpack_out_of_line};
543 use crate::{
544 buffer::LanceBuffer,
545 compression::MiniBlockDecompressor,
546 data::{BlockInfo, DataBlock, FixedWidthDataBlock},
547 testing::{TestCases, check_round_trip_encoding_of_data},
548 version::LanceFileVersion,
549 };
550
551 #[rstest]
552 #[case::u8(8)]
553 #[case::u16(16)]
554 #[case::u32(32)]
555 #[case::u64(64)]
556 fn test_inline_bitpacking_decompress_empty_miniblock(#[case] bit_width: u64) {
557 let decompressor = InlineBitpacking::new(bit_width);
558 let decompressed =
559 MiniBlockDecompressor::decompress(&decompressor, vec![LanceBuffer::empty()], 0)
560 .unwrap();
561
562 let DataBlock::FixedWidth(block) = decompressed else {
563 panic!("Expected FixedWidth block");
564 };
565 assert_eq!(block.bits_per_value, bit_width);
566 assert_eq!(block.num_values, 0);
567 assert_eq!(block.data.len(), 0);
568 }
569
570 #[test_log::test(tokio::test)]
571 async fn test_miniblock_bitpack() {
572 let test_cases = TestCases::default().with_min_file_version(LanceFileVersion::V2_1);
573
574 let arrays = vec![
575 Arc::new(Int8Array::from(vec![100; 1024])) as Arc<dyn Array>,
576 Arc::new(Int8Array::from(vec![1; 1024])) as Arc<dyn Array>,
577 Arc::new(Int8Array::from(vec![16; 1024])) as Arc<dyn Array>,
578 Arc::new(Int8Array::from(vec![-1; 1024])) as Arc<dyn Array>,
579 Arc::new(Int8Array::from(vec![5; 1])) as Arc<dyn Array>,
580 ];
581 check_round_trip_encoding_of_data(arrays, &test_cases, HashMap::new()).await;
582
583 for data_type in [DataType::Int16, DataType::Int32, DataType::Int64] {
584 let int64_arrays = vec![
585 Int64Array::from(vec![3; 1024]),
586 Int64Array::from(vec![8; 1024]),
587 Int64Array::from(vec![16; 1024]),
588 Int64Array::from(vec![100; 1024]),
589 Int64Array::from(vec![512; 1024]),
590 Int64Array::from(vec![1000; 1024]),
591 Int64Array::from(vec![2000; 1024]),
592 Int64Array::from(vec![-1; 10]),
593 ];
594
595 let mut arrays = vec![];
596 for int64_array in int64_arrays {
597 arrays.push(arrow_cast::cast(&int64_array, &data_type).unwrap());
598 }
599
600 check_round_trip_encoding_of_data(arrays, &test_cases, HashMap::new()).await;
601 }
602 }
603
604 #[test_log::test(tokio::test)]
605 async fn test_bitpack_encoding_verification() {
606 use arrow_array::Int32Array;
607
608 let test_cases = TestCases::default()
610 .with_expected_encoding("inline_bitpacking")
611 .with_min_file_version(LanceFileVersion::V2_1);
612
613 let mut values = Vec::new();
616 for i in 0..2048 {
617 values.push(i % 16); }
619
620 let arrays = vec![Arc::new(Int32Array::from(values)) as Arc<dyn Array>];
621
622 let mut metadata = HashMap::new();
624 metadata.insert("lance-encoding:bss".to_string(), "off".to_string());
625
626 check_round_trip_encoding_of_data(arrays, &test_cases, metadata.clone()).await;
627 }
628
629 #[test_log::test(tokio::test)]
630 async fn test_miniblock_bitpack_zero_chunk_selection() {
631 use arrow_array::Int32Array;
632
633 let test_cases = TestCases::default()
634 .with_expected_encoding("inline_bitpacking")
635 .with_min_file_version(LanceFileVersion::V2_1);
636
637 let mut vals = vec![0i32; 1024];
640 for i in 0..1024 {
641 vals.push(i % 16);
642 }
643
644 let arrays = vec![Arc::new(Int32Array::from(vals)) as Arc<dyn Array>];
645
646 let mut metadata = HashMap::new();
648 metadata.insert("lance-encoding:bss".to_string(), "off".to_string());
649 metadata.insert("lance-encoding:rle-threshold".to_string(), "0".to_string());
650
651 check_round_trip_encoding_of_data(arrays, &test_cases, metadata).await;
652 }
653
654 #[test]
655 fn test_out_of_line_bitpack_raw_tail_roundtrip() {
656 let bit_width = 8usize;
657 let word_bits = std::mem::size_of::<u32>() as u64 * 8;
658 let values: Vec<u32> = (0..1025).map(|i| (i % 200) as u32).collect();
659 let input = FixedWidthDataBlock {
660 data: LanceBuffer::reinterpret_vec(values.clone()),
661 bits_per_value: word_bits,
662 num_values: values.len() as u64,
663 block_info: BlockInfo::new(),
664 };
665
666 let compressed = bitpack_out_of_line::<u32>(input, bit_width);
667 let compressed_words = compressed.borrow_to_typed_slice::<u32>().to_vec();
668 let words_per_chunk = (ELEMS_PER_CHUNK as usize * bit_width).div_ceil(word_bits as usize);
669 assert_eq!(
670 compressed_words.len(),
671 words_per_chunk + (values.len() - ELEMS_PER_CHUNK as usize),
672 );
673
674 let compressed_block = FixedWidthDataBlock {
675 data: LanceBuffer::reinterpret_vec(compressed_words.clone()),
676 bits_per_value: word_bits,
677 num_values: compressed_words.len() as u64,
678 block_info: BlockInfo::new(),
679 };
680
681 let decoded = unpack_out_of_line::<u32>(compressed_block, values.len(), bit_width);
682 let decoded_values = decoded.data.borrow_to_typed_slice::<u32>();
683 assert_eq!(decoded_values.as_ref(), values.as_slice());
684 }
685}