Skip to content

Commit

Permalink
Lots of fixes.
Browse files Browse the repository at this point in the history
No more attributes for derive macros. Use `alkahest` attribute.
Unsized formulas never exactly-sized.
Move packet writing into separate module
  • Loading branch information
zakarumych committed May 2, 2023
1 parent 47e162b commit b8878f5
Show file tree
Hide file tree
Showing 35 changed files with 1,411 additions and 1,250 deletions.
File renamed without changes.
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.3.0]

### Fixed

* Fix recursive types.

### Added

* Custom attribute to derive `Formula`, `Serialize` and `Deserialize`
when additional data is needed for deriving.

### Changed

* Derive macros do not accept attributes anymore.
Use custom attribute instead.
* Support manual generic bounds for formula deriving macro.
* Move packet writing into separate trait.

## [0.2.0]

* Reimplement with no unsafe code.
* Fuse deserialization with cheap direct access and lazy deserialization
Expand Down
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "alkahest"
version = "0.2.0-rc.9"
version = "0.3.0"
edition = "2021"
authors = ["Zakarum <[email protected]>"]
license = "MIT OR Apache-2.0"
Expand All @@ -14,19 +14,20 @@ description = "Fantastic serialization library with zero-overhead serialization
alloc = [] # enables impls for types from `alloc` crate.
std = ["alloc"]
derive = ["alkahest-proc"]
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.
default = ["alloc", "fixed32"]
default = ["alloc", "fixed32", "inline-more"]

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

[dependencies]
alkahest-proc = { version = "=0.2.0-rc.9", path = "proc", optional = true }
alkahest-proc = { version = "=0.3.0", path = "proc", optional = true }
bincode = { version = "1.3", optional = true }
serde = { version = "1.0", optional = true }

Expand All @@ -37,5 +38,9 @@ rand = { version = "0.8", features = ["small_rng"] }
name = "test"
required-features = ["derive", "alloc"]

[[example]]
name = "profile"
required-features = ["derive", "alloc"]

[workspace]
members = ["proc", "benchmark"]
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ This benchmark that mimics some game networking protocol.

| | `alkahest` | `bincode` | `rkyv` | `speedy` |
|:----------------|:-------------------------|:--------------------------------|:--------------------------------|:-------------------------------- |
| **`serialize`** | `10.75 us` (✅ **1.00x**) | `10.95 us` (✅ **1.02x slower**) | `12.24 us` (❌ *1.14x slower*) | `11.03 us` (✅ **1.03x slower**) |
| **`read`** | `1.43 us` (✅ **1.00x**) | `9.27 us` (❌ *6.47x slower*) | `2.13 us` (❌ *1.49x slower*) | `1.54 us` (**1.07x slower**) |
| **`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)

See also [benchmark results](./benches/BENCHMARKS.md) from <https://github.com/djkoloski/rust_serialization_benchmark> (in draft until 0.2 release).

See also [benchmark results](./BENCHMARKS.md) from <https://github.com/djkoloski/rust_serialization_benchmark> (in draft until 0.2 release).

## Features

Expand Down Expand Up @@ -122,7 +125,7 @@ The crate works using three fundamental traits.
`Formula`, `Serialize` and `Deserialize`.
There's also supporting trait - `BareFormula`.

*Alkahest* provides derive macros for `Formula`, `Serialize` and `Deserialize`.
*Alkahest* provides proc-macro `alkahest` for deriving `Formula`, `Serialize` and `Deserialize`.

### Formula

Expand Down Expand Up @@ -262,10 +265,11 @@ formula, naturally it will be serialized using `bincode` crate.
// This requires two default features - "alloc" and "derive".
#[cfg(all(feature = "derive", feature = "alloc"))]
fn main() {
use alkahest::{Formula, Serialize, Deserialize, serialize_to_vec, deserialize};
use alkahest::{alkahest, serialize_to_vec, deserialize};

// Define simple formula. Make it self-serializable.
#[derive(Clone, Debug, PartialEq, Eq, Formula, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq)]
#[alkahest(Formula, SerializeRef, Deserialize)]
struct MyDataType {
a: u32,
b: Vec<u8>,
Expand All @@ -284,10 +288,9 @@ fn main() {
// This is default behavior for `Serialized` derive macro.
// Some types required ownership transfer for serialization.
// Notable example is iterators.
let size = serialize_to_vec::<MyDataType, _>(&value, &mut data);
let (size, _) = serialize_to_vec::<MyDataType, _>(&value, &mut data);

let (de, de_size) = deserialize::<MyDataType, MyDataType>(&data).unwrap();
assert_eq!(de_size, size);
let de = deserialize::<MyDataType, MyDataType>(&data[..size]).unwrap();
assert_eq!(de, value);
}

Expand Down
12 changes: 12 additions & 0 deletions benchmark/bench.json

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions benchmark/benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern crate rkyv;
#[cfg(feature = "speedy")]
extern crate speedy;

use alkahest::{Deserialize, Formula, Lazy, Ref, SerIter, Serialize};
use alkahest::{alkahest, Deserialize, Formula, Lazy, Ref, SerIter, Serialize};
use criterion::{black_box, criterion_group, criterion_main, Criterion};

#[cfg(feature = "rkyv")]
Expand All @@ -32,9 +32,9 @@ pub enum GameMessage {
Server(ServerMessage),
}

#[derive(Debug, Deserialize)]
#[derive(Debug)]
#[cfg_attr(feature = "speedy", derive(speedy::Readable))]
#[alkahest(GameMessage)]
#[alkahest(Deserialize<'de, GameMessage>)]
pub enum GameMessageRead<'de> {
Client(ClientMessageRead<'de>),
Server(ServerMessageRead<'de>),
Expand All @@ -50,9 +50,9 @@ pub enum ClientMessage {
Chat(String),
}

#[derive(Debug, Deserialize)]
#[derive(Debug)]
#[cfg_attr(feature = "speedy", derive(speedy::Readable))]
#[alkahest(ClientMessage)]
#[alkahest(Deserialize<'de, ClientMessage>)]
pub enum ClientMessageRead<'de> {
ClientData { nickname: &'de str, clan: &'de str },
Chat(&'de str),
Expand All @@ -68,9 +68,9 @@ pub enum ServerMessage {
ClientChat { client_id: u64, message: String },
}

#[derive(Debug, Deserialize)]
#[derive(Debug)]
#[cfg_attr(feature = "speedy", derive(speedy::Readable))]
#[alkahest(ServerMessage)]
#[alkahest(Deserialize<'de, ServerMessage>)]
pub enum ServerMessageRead<'de> {
ServerData(u64),
ClientChat { client_id: u64, message: &'de str },
Expand All @@ -85,14 +85,14 @@ pub struct NetPacket<G> {
pub game_messages: Vec<G>,
}

#[derive(Debug, Serialize)]
#[alkahest(owned(for<X: Formula> NetPacket<X> where G: Serialize<[X]>))]
#[derive(Debug)]
#[alkahest(for<X: Formula> Serialize<NetPacket<X>> where G: Serialize<[X]>)]
pub struct NetPacketWrite<G> {
pub game_messages: G,
}

#[derive(Debug, Deserialize)]
#[alkahest(NetPacket<G> where G: Formula)]
#[derive(Debug)]
#[alkahest(Deserialize<'de, NetPacket<G>> where G: Formula)]
pub struct NetPacketRead<'de, G> {
pub game_messages: Lazy<'de, [G]>,
}
Expand Down Expand Up @@ -126,26 +126,28 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let mut rng = SmallRng::seed_from_u64(42);

const LEN: usize = 200;
let mut size = 0;

{
let mut group = c.benchmark_group("net-packet/alkahest");
group.bench_function("serialize", |b| {
b.iter(|| {
alkahest::serialize_to_vec::<NetPacket<GameMessage>, _>(
size = alkahest::serialize_to_vec::<NetPacket<GameMessage>, _>(
NetPacketWrite {
game_messages: SerIter(messages(rng.clone(), black_box(LEN))),
},
&mut buffer,
);
)
.0;
})
});

group.bench_function("read", |b| {
b.iter(|| {
let (packet, _) = alkahest::deserialize::<
let packet = alkahest::deserialize::<
NetPacket<GameMessage>,
NetPacketRead<GameMessage>,
>(&buffer[..])
>(&buffer[..size])
.unwrap();

for message in packet.game_messages.iter::<GameMessageRead>() {
Expand Down
85 changes: 85 additions & 0 deletions examples/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::{hint::black_box, mem::size_of};

use alkahest::*;
use rand::{distributions::Standard, prelude::Distribution, Rng};

#[derive(Clone, Copy)]
#[alkahest(Formula, Serialize, SerializeRef, Deserialize)]
pub struct Vector3 {
pub x: f32,
pub y: f32,
pub z: f32,
}

impl Distribution<Vector3> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vector3 {
Vector3 {
x: rng.gen(),
y: rng.gen(),
z: rng.gen(),
}
}
}

#[derive(Clone, Copy)]
#[alkahest(Formula, Serialize, SerializeRef, Deserialize)]
pub struct Triangle {
pub v0: Vector3,
pub v1: Vector3,
pub v2: Vector3,
pub normal: Vector3,
}

impl Distribution<Triangle> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Triangle {
let v0 = rng.gen();
let v1 = rng.gen();
let v2 = rng.gen();
let normal = rng.gen();

Triangle { v0, v1, v2, normal }
}
}

#[alkahest(Formula)]
pub struct MeshFormula {
pub triangles: [Triangle],
}

#[derive(Clone)]
#[alkahest(Serialize<MeshFormula>, SerializeRef<MeshFormula>, Deserialize<'_, MeshFormula>)]
pub struct Mesh {
pub triangles: Vec<Triangle>,
}

#[alkahest(Deserialize<'a, MeshFormula>)]
pub struct LazyMesh<'a> {
pub triangles: Lazy<'a, [Triangle]>,
}

#[inline(always)]
fn do_serialize(mesh: &Mesh, buffer: &mut [u8]) -> usize {
alkahest::write_packet_unchecked::<MeshFormula, _>(&mesh, buffer)
}

fn main() {
const TRIG_COUNT: usize = 100_000;

let mesh = Mesh {
triangles: rand::thread_rng()
.sample_iter(Standard)
.take(TRIG_COUNT)
.collect(),
};

let mut mesh = black_box(mesh);

let mut buffer = Vec::new();
buffer.resize(TRIG_COUNT * size_of::<Triangle>() + 32, 0);

for _ in 0..10_000 {
let size = do_serialize(&mesh, &mut buffer);
black_box(&buffer[..size]);
mesh = black_box(mesh);
}
}
Loading

0 comments on commit b8878f5

Please sign in to comment.