Skip to content

Commit

Permalink
Use memcpy to encode/decode Vec<[int; N]>.
Browse files Browse the repository at this point in the history
  • Loading branch information
caibear committed Mar 22, 2024
1 parent 5655c92 commit 2c7fc8e
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 17 deletions.
45 changes: 45 additions & 0 deletions src/derive/array.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::coder::{Buffer, Decoder, Encoder, Result, View};
use crate::consume::mul_length;
use crate::derive::{Decode, Encode};
use crate::fast::{FastSlice, FastVec, Unaligned};
use std::mem::MaybeUninit;
use std::num::NonZeroUsize;

Expand All @@ -15,6 +16,23 @@ impl<T: Encode, const N: usize> Default for ArrayEncoder<T, N> {
}

impl<T: Encode, const N: usize> Encoder<[T; N]> for ArrayEncoder<T, N> {
fn as_primitive(&mut self) -> Option<&mut FastVec<[T; N]>> {
// FastVec doesn't work on ZST.
if N == 0 {
return None;
}
self.0.as_primitive().map(|v| {
debug_assert!(v.len() % N == 0);
// Safety: FastVec uses pointers for len/cap unlike Vec, so casting to FastVec<[T; N]>
// is safe as long as `v.len() % N == 0`. This will always be the case since we only
// encode in chunks of N.
// NOTE: If panics occurs during ArrayEncoder::encode and Buffer is reused, this
// invariant can be violated. Luckily primitive encoders never panic.
// TODO std::mem::take Buffer while encoding to avoid corrupted buffers.
unsafe { std::mem::transmute(v) }
})
}

#[inline(always)]
fn encode(&mut self, array: &[T; N]) {
// TODO use encode_vectored if N is large enough.
Expand Down Expand Up @@ -59,6 +77,14 @@ impl<'a, T: Decode<'a>, const N: usize> View<'a> for ArrayDecoder<'a, T, N> {
}

impl<'a, T: Decode<'a>, const N: usize> Decoder<'a, [T; N]> for ArrayDecoder<'a, T, N> {
fn as_primitive(&mut self) -> Option<&mut FastSlice<Unaligned<[T; N]>>> {
self.0.as_primitive().map(|s| {
// Safety: FastSlice doesn't have a length unlike slice, so casting to FastSlice<[T; N]>
// is safe. N == 0 case is also safe for the same reason.
unsafe { std::mem::transmute(s) }
})
}

#[inline(always)]
fn decode_in_place(&mut self, out: &mut MaybeUninit<[T; N]>) {
// Safety: Equivalent to nightly MaybeUninit::transpose.
Expand All @@ -68,3 +94,22 @@ impl<'a, T: Decode<'a>, const N: usize> Decoder<'a, [T; N]> for ArrayDecoder<'a,
}
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_empty_array() {
type T = [u8; 0];
let empty_array = T::default();
crate::decode::<T>(&crate::encode(&empty_array)).unwrap();
crate::decode::<Vec<T>>(&crate::encode(&vec![empty_array; 100])).unwrap();
}

fn bench_data() -> Vec<Vec<[u8; 3]>> {
crate::random_data::<u8>(125)
.into_iter()
.map(|n| (0..n / 16).map(|_| [0, 0, 255]).collect())
.collect()
}
crate::bench_encode_decode!(u8_array_vecs: Vec<Vec<[u8; 3]>>);
}
26 changes: 9 additions & 17 deletions src/fast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<T> From<Vec<T>> for FastVec<T> {
}

impl<T> FastVec<T> {
fn len(&self) -> usize {
pub fn len(&self) -> usize {
sub_ptr(self.end, self.start)
}

Expand Down Expand Up @@ -212,7 +212,7 @@ impl<'a, T: Copy, const N: usize> PushUnchecked<T> for FastArrayVec<'a, T, N> {
pub struct FastSlice<'a, T> {
ptr: *const T,
#[cfg(debug_assertions)]
len: usize, // TODO could store end ptr to allow Debug and as_slice.
end: *const T, // Uses pointer instead of len to permit &mut FastSlice<T> -> &mut FastSlice<[T; N]> cast.
_spooky: PhantomData<&'a T>,
}

Expand All @@ -233,7 +233,7 @@ impl<'a, T> From<&'a [T]> for FastSlice<'a, T> {
Self {
ptr: slice.as_ptr(),
#[cfg(debug_assertions)]
len: slice.len(),
end: slice.as_ptr_range().end,
_spooky: PhantomData,
}
}
Expand All @@ -247,7 +247,7 @@ impl<'a, T> FastSlice<'a, T> {
Self {
ptr,
#[cfg(debug_assertions)]
len,
end: ptr.wrapping_add(len),
_spooky: PhantomData,
}
}
Expand All @@ -256,9 +256,7 @@ impl<'a, T> FastSlice<'a, T> {
#[inline(always)]
pub unsafe fn next_unchecked_as_ptr(&mut self) -> *const T {
#[cfg(debug_assertions)]
{
self.len = self.len.checked_sub(1).unwrap();
}
assert!((self.ptr.wrapping_add(1) as usize) <= self.end as usize);
let p = self.ptr;
self.ptr = self.ptr.add(1);
p
Expand All @@ -267,9 +265,7 @@ impl<'a, T> FastSlice<'a, T> {
#[inline(always)]
pub unsafe fn advance(&mut self, n: usize) {
#[cfg(debug_assertions)]
{
self.len = self.len.checked_sub(n).unwrap();
}
assert!((self.ptr.wrapping_add(n) as usize) <= self.end as usize);
self.ptr = self.ptr.add(n);
}

Expand Down Expand Up @@ -307,9 +303,7 @@ impl<'a, T: Copy> NextUnchecked<'a, T> for FastSlice<'a, T> {
#[inline(always)]
unsafe fn next_unchecked(&mut self) -> T {
#[cfg(debug_assertions)]
{
self.len = self.len.checked_sub(1).unwrap();
}
assert!((self.ptr.wrapping_add(1) as usize) <= self.end as usize);
let t = *self.ptr;
self.ptr = self.ptr.add(1);
t
Expand All @@ -318,9 +312,7 @@ impl<'a, T: Copy> NextUnchecked<'a, T> for FastSlice<'a, T> {
#[inline(always)]
unsafe fn chunk_unchecked(&mut self, length: usize) -> &'a [T] {
#[cfg(debug_assertions)]
{
self.len = self.len.checked_sub(length).unwrap();
}
assert!((self.ptr.wrapping_add(length) as usize) <= self.end as usize);
let slice = std::slice::from_raw_parts(self.ptr, length);
self.ptr = self.ptr.add(length);
slice
Expand Down Expand Up @@ -373,7 +365,7 @@ impl<'borrowed, T> CowSlice<'borrowed, T> {
'borrowed: 'me,
{
#[cfg(debug_assertions)]
assert_eq!(self.slice.len, len);
assert_eq!(self.slice.ptr.wrapping_add(len), self.slice.end);
std::slice::from_raw_parts(self.slice.ptr, len)
}

Expand Down

0 comments on commit 2c7fc8e

Please sign in to comment.