Skip to content

Commit

Permalink
Remove FixedUsize and FixedIsize wrappers
Browse files Browse the repository at this point in the history
Use usize and isize directly.
  • Loading branch information
zakarumych committed Dec 28, 2023
1 parent 088e92c commit 314d56b
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 343 deletions.
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,24 @@ inline-more = []

## TODO: Control on value or type level?
## Keep features for defaults?
fixed8 = [] # sets size of `FixedUsize` and `FixedIsize` to 8 bits.
fixed16 = [] # sets size of `FixedUsize` and `FixedIsize` to 16 bits.
fixed32 = [] # sets size of `FixedUsize` and `FixedIsize` to 32 bits. Default.
fixed64 = [] # sets size of `FixedUsize` and `FixedIsize` to 64 bits.
fixed8 = [] # sets size of `usize` and `isize` to 8 bits.
fixed16 = [] # sets size of `usize` and `isize` to 16 bits.
fixed32 = [] # sets size of `usize` and `isize` to 32 bits. Default.
fixed64 = [] # sets size of `usize` and `isize` to 64 bits.

default = ["alloc", "fixed32", "inline-more"]

bincoded = ["dep:bincode", "dep:serde", "std"]

[dependencies]
alkahest-proc = { version = "=0.3.0", path = "proc", optional = true }
cfg-if = { version = "1.0" }
bincode = { version = "1.3", optional = true }
serde = { version = "1.0", optional = true }

[dev-dependencies]
rand = { version = "0.8", features = ["small_rng"] }
serde = { version = "1.0", features = ["derive"] }

[[example]]
name = "test"
Expand Down
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ custom high-performance network protocols.

This benchmark that mimics some game networking protocol.

| | `alkahest` | `bincode` | `rkyv` | `speedy` |
|:----------------|:-------------------------|:--------------------------------|:--------------------------------|:-------------------------------- |
| **`serialize`** | `10.69 us` (✅ **1.00x**) | `11.08 us` (✅ **1.04x slower**) | `12.43 us` (❌ *1.16x slower*) | `11.13 us` (✅ **1.04x slower**) |
| **`read`** | `1.19 us` (✅ **1.00x**) | `9.19 us` (❌ *7.74x slower*) | `2.10 us` (❌ *1.77x slower*) | `1.54 us` (❌ *1.30x slower*) |
| | `alkahest` | `bincode` | `rkyv` | `speedy` |
| :-------------- | :----------------------- | :------------------------------ | :---------------------------- | :------------------------------ |
| **`serialize`** | `10.69 us` (✅ **1.00x**) | `11.08 us` (✅ **1.04x slower**) | `12.43 us` (❌ *1.16x slower*) | `11.13 us` (✅ **1.04x slower**) |
| **`read`** | `1.19 us` (✅ **1.00x**) | `9.19 us` (❌ *7.74x slower*) | `2.10 us` (❌ *1.77x slower*) | `1.54 us` (❌ *1.30x slower*) |

---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
Expand Down Expand Up @@ -140,14 +140,11 @@ opening possibility for cross-language communication.

`Formula` is implemented for a number of types out-of-the-box.
Primitive types like `bool`, integers and floating point types all implement `Formula`.
This excludes `isize` and `usize`.
In their place there's `FixedUsize` and `FixedIsize` types provided,
whose size is controlled by a feature-flag.
*!Caveat!*:
Sizes and addresses are serialized as `FixedUsize`.
Serialized size of `isize` and `usize` is controlled by a feature-flag.
Sizes and addresses are serialized as `usize`.
Truncating `usize` value if it was too large.
This may result in broken data generated and panic in debug.
Increase size of the `FixedUsize` if you encounter this.
It is also implemented for tuples, array and slice, `Option` and `Vec` (the later requires `"alloc"` feature).

The easiest way to define a new formula is to derive `Formula` trait for a struct or an enum.
Expand Down
81 changes: 76 additions & 5 deletions src/bincoded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
deserialize::{Deserialize, DeserializeError, Deserializer},
formula::{reference_size, Formula},
serialize::{write_reference, Serialize, Sizes},
size::{FixedUsize, FixedUsizeType},
size::FixedUsizeType,
};

/// A formula that can be used to serialize and deserialize data
Expand Down Expand Up @@ -40,12 +40,9 @@ where
};

let Ok(size) = FixedUsizeType::try_from(size) else {
panic!("Bincode serialization uses more that `FixedUsize::MAX` bytes");
panic!("Bincode serialization uses more that `FixedUsizeType::MAX` bytes");
};

let Ok(size) = FixedUsize::try_from(size) else {
panic!("Bincode serialization uses more that `usize::MAX` bytes");
};
let size: usize = size.into();

match buffer.reserve_heap(sizes.heap, sizes.stack, size) {
Expand Down Expand Up @@ -175,3 +172,77 @@ where
<T as Deserialize<'de, Bincode>>::deserialize_in_place(self, de)
}
}

#[test]
fn roundtrip() {
use alkahest::{alkahest, Bincoded};

#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct BincodedStruct {
bytes: Box<[u8]>,
}

#[derive(Clone)]
#[alkahest(Serialize<BincodedWrapperFormula>, Deserialize<'_, BincodedWrapperFormula>)]
pub struct BincodedWrapperStruct {
wrapped: BincodedStruct,
}

#[alkahest(Formula)]
pub struct BincodedWrapperFormula {
wrapped: Bincoded<BincodedStruct>,
}

#[derive(Clone)]
#[alkahest(Serialize<VariableHeaderV1Formula>)]
pub(crate) struct VariableHeaderV1Construction {
// A filter covering all keys within the table.
pub(crate) bincoded: BincodedWrapperStruct,
pub(crate) bytes: Vec<u8>,
}

#[alkahest(Deserialize<'de, VariableHeaderV1Formula>)]
pub(crate) struct VariableHeaderV1Access<'de> {
// A filter covering all keys within the table.
pub(crate) bincoded: BincodedWrapperStruct,
pub(crate) bytes: &'de [u8],
}

#[alkahest(Formula)]
pub(crate) struct VariableHeaderV1Formula {
bincoded: BincodedWrapperFormula,
bytes: alkahest::Bytes,
}

let bincoded = BincodedWrapperStruct {
wrapped: BincodedStruct {
bytes: Box::new([4, 5, 6, 7]),
},
};
let header = VariableHeaderV1Construction {
bincoded,
bytes: vec![1, 2, 3, 4],
};
let mut output = vec![0u8; 4096];
let (serialized_len, size) =
alkahest::serialize::<VariableHeaderV1Formula, _>(header, &mut output).unwrap();
output.truncate(serialized_len);

let deserialized = alkahest::deserialize_with_size::<
VariableHeaderV1Formula,
VariableHeaderV1Access,
>(&output, size)
.unwrap();
assert_eq!(
&*deserialized.bincoded.wrapped.bytes,
&[4, 5, 6, 7],
"Full serialized {:?}",
&output
);
assert_eq!(
deserialized.bytes,
&[1, 2, 3, 4],
"Full serialized {:?}",
&output
);
}
29 changes: 20 additions & 9 deletions src/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::{any::type_name, iter::FusedIterator, marker::PhantomData, str::Utf8Er

use crate::{
formula::{reference_size, unwrap_size, Formula},
size::{FixedIsizeType, FixedUsize, FixedUsizeType, SIZE_STACK},
size::{deserialize_usize, FixedIsizeType, FixedUsizeType, SIZE_STACK},
};

#[inline(always)]
Expand Down Expand Up @@ -197,6 +197,17 @@ impl<'de> Deserializer<'de> {
&self.input[at..]
}

/// Reads and deserializes usize from the input buffer.
/// Advances the input buffer.
///
/// # Errors
///
/// Returns `DeserializeError` if deserialization fails.
#[inline(always)]
pub fn read_usize(&mut self) -> Result<usize, DeserializeError> {
deserialize_usize(self.sub(SIZE_STACK)?)
}

/// Reads and deserializes field from the input buffer.
/// Advances the input buffer.
///
Expand All @@ -210,7 +221,7 @@ impl<'de> Deserializer<'de> {
T: Deserialize<'de, F>,
{
let stack = match (F::MAX_STACK_SIZE, F::EXACT_SIZE, last) {
(None, _, false) => self.read_value::<FixedUsize, usize>(false)?,
(None, _, false) => self.read_usize()?,
(None, _, true) => self.stack,
(Some(max_stack), false, true) => max_stack.min(self.stack),
(Some(max_stack), _, _) => max_stack,
Expand Down Expand Up @@ -259,7 +270,7 @@ impl<'de> Deserializer<'de> {
let stack = match (last, F::MAX_STACK_SIZE) {
(true, _) => self.stack,
(false, Some(max_stack)) => max_stack,
(false, None) => self.read_value::<FixedUsize, usize>(false)?,
(false, None) => self.read_usize()?,
};

<T as Deserialize<'de, F>>::deserialize_in_place(place, self.sub(stack)?)
Expand Down Expand Up @@ -308,7 +319,7 @@ impl<'de> Deserializer<'de> {
{
let upper = match F::MAX_STACK_SIZE {
None => panic!("Formula must be sized"),
Some(0) => self.read_value::<FixedUsize, usize>(true).unwrap_or(0),
Some(0) => self.read_usize().unwrap_or(0),
Some(max_stack) => self.stack / max_stack,
};

Expand All @@ -331,7 +342,7 @@ impl<'de> Deserializer<'de> {
{
let upper = match F::MAX_STACK_SIZE {
None => self.stack / SIZE_STACK,
Some(0) => self.read_value::<FixedUsize, usize>(true).unwrap_or(0),
Some(0) => self.read_usize().unwrap_or(0),
Some(max_stack) => self.stack / max_stack,
};

Expand Down Expand Up @@ -406,7 +417,7 @@ impl<'de> Deserializer<'de> {
match F::MAX_STACK_SIZE {
None => {
for _ in 0..n {
let skip_bytes = self.read_value::<FixedUsize, usize>(false)?;
let skip_bytes = self.read_usize()?;
self.read_bytes(skip_bytes)?;
}
}
Expand Down Expand Up @@ -536,7 +547,7 @@ where
let sub = Deserializer::new_unchecked(SIZE_STACK, self.de.input);
self.de.input = &self.de.input[..self.de.input.len() - SIZE_STACK];

let stack = match <usize as Deserialize<'de, FixedUsize>>::deserialize(sub) {
let stack = match deserialize_usize(sub) {
Ok(stack) => stack,
Err(err) => {
self.de.stack = 0;
Expand Down Expand Up @@ -779,13 +790,13 @@ where

if F::EXACT_SIZE {
let mut de = Deserializer::new(reference_size, &input[..reference_size]).unwrap();
let Ok(address) = de.read_value::<FixedUsize, usize>(true) else {
let Ok(address) = de.read_usize() else {
unreachable!();
};
(address, unwrap_size(F::MAX_STACK_SIZE).min(len))
} else {
let mut de = Deserializer::new(reference_size, &input[..reference_size]).unwrap();
let Ok([size, address]) = de.read_value::<[FixedUsize; 2], [usize; 2]>(true) else {
let Ok([size, address]) = de.read_value::<[usize; 2], [usize; 2]>(true) else {
unreachable!();
};
(address, size)
Expand Down
8 changes: 4 additions & 4 deletions src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
deserialize::DeserializeError,
formula::Formula,
serialize::{field_size_hint, write_slice, Serialize, Sizes},
size::{FixedUsize, SIZE_STACK},
size::SIZE_STACK,
};

const ITER_UPPER: usize = 4;
Expand Down Expand Up @@ -258,7 +258,7 @@ where

// Typically `usize` is not serializable.
// But lib makes exception for `usize`s that are derived from actual sizes.
impl<F, I, T> Serialize<[(FixedUsize, F)]> for core::iter::Enumerate<I>
impl<F, I, T> Serialize<[(usize, F)]> for core::iter::Enumerate<I>
where
F: Formula,
I: Iterator<Item = T>,
Expand All @@ -269,12 +269,12 @@ where
where
B: Buffer,
{
serialize_iter_to_slice!((FixedUsize, F) : self => sizes, buffer)
serialize_iter_to_slice!((usize, F) : self => sizes, buffer)
}

#[inline(always)]
fn size_hint(&self) -> Option<Sizes> {
default_iter_fast_sizes::<(FixedUsize, F), _>(self)
default_iter_fast_sizes::<(usize, F), _>(self)
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ pub use crate::{
serialize, serialize_or_size, serialize_unchecked, serialized_size, BufferSizeRequired,
Serialize, SerializeRef,
},
size::{FixedIsize, FixedUsize},
skip::Skip,
vlq::Vlq,
};
Expand All @@ -97,7 +96,7 @@ pub mod advanced {
write_exact_size_field, write_field, write_ref, write_reference, write_slice, Sizes,
SliceWriter,
},
size::{FixedIsize, FixedIsizeType},
size::{FixedIsizeType, FixedUsizeType},
};

#[cfg(feature = "alloc")]
Expand Down
10 changes: 6 additions & 4 deletions src/packet.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::{
advanced::FixedUsizeType,
buffer::{Buffer, BufferExhausted, CheckedFixedBuffer, DryBuffer, VecBuffer},
deserialize::{read_reference, Deserialize, DeserializeError, Deserializer},
formula::{reference_size, Formula},
serialize::{write_ref, write_reference, Serialize, Sizes},
size::{FixedUsize, SIZE_STACK},
size::SIZE_STACK,
};

/// Returns the number of bytes required to write packet with the value.
Expand Down Expand Up @@ -129,9 +130,10 @@ where
} else {
let mut bytes = [0u8; SIZE_STACK];
bytes.copy_from_slice(&input[..SIZE_STACK]);
let address =
FixedUsize::from_le_bytes(bytes).expect("Value size can't fit `usize`");
Some(address.into())
let address = FixedUsizeType::from_le_bytes(bytes)
.try_into()
.expect("Value size can't fit `usize`");
Some(address)
}
}
}
Expand Down
17 changes: 7 additions & 10 deletions src/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use core::{fmt, marker::PhantomData, ops};
use crate::{
buffer::{Buffer, BufferExhausted, CheckedFixedBuffer, DryBuffer, MaybeFixedBuffer},
formula::{unwrap_size, BareFormula, Formula},
size::{FixedUsize, SIZE_STACK},
size::{usize_truncate_unchecked, SIZE_STACK},
};

#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -492,14 +492,11 @@ where
F: Formula + ?Sized,
B: Buffer,
{
let address = FixedUsize::truncate_unchecked(address);
let size = FixedUsize::truncate_unchecked(size);
let address = usize_truncate_unchecked(address);
let size = usize_truncate_unchecked(size);

if F::EXACT_SIZE {
debug_assert_eq!(
size,
FixedUsize::truncate_unchecked(F::MAX_STACK_SIZE.unwrap())
);
debug_assert_eq!(size, usize_truncate_unchecked(F::MAX_STACK_SIZE.unwrap()));
buffer.write_stack(heap, stack, &address.to_le_bytes())?;
} else {
buffer.write_stack(heap, stack, &size.to_le_bytes())?;
Expand Down Expand Up @@ -537,7 +534,7 @@ where

match (F::MAX_STACK_SIZE, F::EXACT_SIZE, last) {
(None, _, false) => {
let size = FixedUsize::truncate_unchecked(sizes.stack - old_stack);
let size = usize_truncate_unchecked(sizes.stack - old_stack);
let res = buffer.write_stack(sizes.heap, old_stack - SIZE_STACK, &size.to_le_bytes());
if res.is_err() {
unreachable!("Successfully written before");
Expand Down Expand Up @@ -711,7 +708,7 @@ where
pub fn finish(self) -> Result<(), B::Error> {
if let Some(0) = <F as Formula>::MAX_STACK_SIZE {
debug_assert!(<F as Formula>::HEAPLESS);
write_field::<FixedUsize, _, _>(self.count, self.sizes, self.buffer.reborrow(), true)?;
write_field::<usize, _, _>(self.count, self.sizes, self.buffer.reborrow(), true)?;
}
Ok(())
}
Expand Down Expand Up @@ -768,7 +765,7 @@ where
} else {
iter.count()
};
write_field::<FixedUsize, _, _>(count, sizes, buffer, true)
write_field::<usize, _, _>(count, sizes, buffer, true)
} else {
iter.try_fold((), |(), elem| {
write_field::<F, _, _>(elem, sizes, buffer.reborrow(), false)
Expand Down
Loading

0 comments on commit 314d56b

Please sign in to comment.