Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ serde = ["encode", "dep:serde"]
[dependencies]
fxhash = "0.2"
bytes = { version = "1", optional = true }
serde = { version = "1", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }

[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
varint-simd = { version = "0.4", optional = true }
Expand All @@ -55,11 +55,13 @@ varint-simd = { version = "0.4", optional = true }
unsigned-varint = { version = "0.8", optional = true }

[dev-dependencies]
bincode = { version = "2", features = ["serde"] }
criterion = "0.7"
pando = { path = ".", features = ["encode", "serde"] }
pando-macros = { path = "macros" }
rand = "0.9"
rand_chacha = "0.9"
serde_json = "1"

[[bench]]
name = "apply_local"
Expand All @@ -74,5 +76,10 @@ name = "encode"
harness = false
required-features = ["encode"]

[[bench]]
name = "serde"
harness = false
required-features = ["serde"]

[lints]
workspace = true
185 changes: 185 additions & 0 deletions benches/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#![allow(missing_docs)]
#![allow(clippy::unwrap_used)]

mod common;

use core::fmt;

use common::TreeSize;
use criterion::{BenchmarkId, Criterion};
use rand::SeedableRng;
use serde::de;

fn bench_serialize<D: DataFormat, M: Metadata>(c: &mut Criterion) {
let mut rng = <rand_chacha::ChaChaRng as SeedableRng>::seed_from_u64(42);

let mut group =
c.benchmark_group(format!("serialize_{}_{}", D::NAME, M::NAME));

for test_vector in TestVector::iter() {
let tree = test_vector.tree_size.to_tree::<M>(&mut rng);
group.bench_function(test_vector.bench_id(), |bencher| {
bencher.iter(|| {
D::serialize(
&tree
.serialize()
.with_tree_state(test_vector.encode_tree_state),
);
})
});
}
}

fn bench_deserialize<D: DataFormat, M: Metadata>(c: &mut Criterion) {
let mut rng = <rand_chacha::ChaChaRng as SeedableRng>::seed_from_u64(42);

let mut group =
c.benchmark_group(format!("deserialize_{}_{}", D::NAME, M::NAME));

for test_vector in TestVector::iter() {
let tree = test_vector.tree_size.to_tree::<M>(&mut rng);
let serialized = D::serialize(
&tree.serialize().with_tree_state(test_vector.encode_tree_state),
);
group.bench_function(test_vector.bench_id(), |bencher| {
bencher.iter(|| {
D::deserialize_seed(
pando::Tree::<M>::deserialize(2),
&serialized,
);
})
});
}
}

trait DataFormat {
const NAME: &str;

type Serialized;

fn serialize(value: &impl serde::Serialize) -> Self::Serialized;

fn deserialize_seed<'de, T: de::DeserializeSeed<'de>>(
seed: T,
serialized: &'de Self::Serialized,
) -> T::Value;
}

trait Metadata:
Default
+ Clone
+ fmt::Debug
+ Eq
+ serde::Serialize
+ serde::de::DeserializeOwned
{
const NAME: &str;
}

struct Bincode;

#[derive(
Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize,
)]
#[serde(transparent)]
struct LargeString<const SIZE: usize = 256> {
inner: String,
}

struct TestVector {
encode_tree_state: bool,
tree_size: TreeSize,
}

impl TestVector {
fn iter() -> impl Iterator<Item = Self> {
let mut items = Vec::new();

for tree_size in TreeSize::iter() {
for encode_tree_state in [true, false] {
items.push(Self { encode_tree_state, tree_size });
}
}

items.into_iter()
}

fn bench_id(&self) -> BenchmarkId {
BenchmarkId::new(
self.tree_size,
if self.encode_tree_state {
"with_state"
} else {
"without_state"
},
)
}
}

impl Metadata for () {
const NAME: &str = "unit";
}

impl Metadata for LargeString {
const NAME: &str = "large_string";
}

impl<const SIZE: usize> Default for LargeString<SIZE> {
fn default() -> Self {
Self { inner: "x".repeat(SIZE) }
}
}

impl DataFormat for Bincode {
const NAME: &str = "bincode";

type Serialized = Vec<u8>;

fn serialize(value: &impl serde::Serialize) -> Self::Serialized {
bincode::serde::encode_to_vec(value, bincode::config::standard())
.expect("failed to serialize with bincode")
}

fn deserialize_seed<'de, T: de::DeserializeSeed<'de>>(
seed: T,
serialized: &'de Self::Serialized,
) -> T::Value {
let (deserialized, _num_read) =
bincode::serde::seed_decode_from_slice(
seed,
serialized,
bincode::config::standard(),
)
.expect("failed to deserialize with bincode");

deserialized
}
}

fn serialize_unit_bincode(c: &mut Criterion) {
bench_serialize::<Bincode, ()>(c);
}

fn serialize_large_string_bincode(c: &mut Criterion) {
bench_serialize::<Bincode, LargeString>(c);
}

fn decode_unit_bincode(c: &mut Criterion) {
bench_deserialize::<Bincode, ()>(c);
}

fn decode_large_string_bincode(c: &mut Criterion) {
bench_deserialize::<Bincode, LargeString>(c);
}

criterion::criterion_group!(
encode,
serialize_unit_bincode,
serialize_large_string_bincode
);
criterion::criterion_group!(
decode,
decode_unit_bincode,
decode_large_string_bincode
);
criterion::criterion_main!(encode, decode);
37 changes: 23 additions & 14 deletions src/forest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use crate::node_id::{GlobalNodeId, LocalNodeId};
use crate::op_log;

#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub(crate) struct Forest {
/// TODO: docs.
forest: Vec<Node>,
nodes: Vec<Node>,

/// Keeps track of the nodes that cannot be added to the visible tree
/// because we haven't yet received the creation of their parent.
Expand All @@ -20,6 +21,7 @@ pub(crate) struct Forest {
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub(crate) struct Node {
/// TODO: docs.
children: Vec<NodeKey>,
Expand All @@ -32,6 +34,8 @@ pub(crate) struct Node {
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub(crate) struct NodeKey(usize);

#[derive(Debug, PartialEq, Eq)]
Expand All @@ -42,6 +46,7 @@ pub(crate) enum NodeState {
}

#[derive(Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Backlog {
/// Map from the [`GlobalNodeId`] of a node whose creation we haven't yet
/// received to the [`NodeKey`]s of all the nodes in the forest that would
Expand Down Expand Up @@ -158,7 +163,7 @@ impl Forest {

let mut id_to_key = FxHashMap::default();
id_to_key.insert(root_id, Self::VISIBLE_ROOT_KEY);
Self { forest: vec![root], backlog: Backlog::default(), id_to_key }
Self { nodes: vec![root], backlog: Backlog::default(), id_to_key }
}

/// TODO: docs.
Expand Down Expand Up @@ -241,10 +246,10 @@ impl Forest {
) -> NodeKey {
debug_assert!(!self.id_to_key.contains_key(&node_id));

let node_key = NodeKey::new(self.forest.len());
let node_key = NodeKey::new(self.nodes.len());

// Insert the new node into the forest.
self.forest.push(Node {
self.nodes.push(Node {
children: Vec::new(),
creation_key: node_creation_key,
parent_key,
Expand Down Expand Up @@ -433,15 +438,15 @@ impl ops::Index<NodeKey> for Forest {
#[track_caller]
#[inline]
fn index(&self, key: NodeKey) -> &Self::Output {
&self.forest[key.into_usize()]
&self.nodes[key.into_usize()]
}
}

impl ops::IndexMut<NodeKey> for Forest {
#[track_caller]
#[inline]
fn index_mut(&mut self, key: NodeKey) -> &mut Self::Output {
&mut self.forest[key.into_usize()]
&mut self.nodes[key.into_usize()]
}
}

Expand Down Expand Up @@ -502,6 +507,10 @@ pub(crate) mod udr {

/// TODO: docs.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize)
)]
pub(crate) enum PrevParent {
/// TODO: docs.
Backlogged,
Expand Down Expand Up @@ -741,7 +750,7 @@ mod invariants {
#[allow(dead_code)]
#[track_caller]
pub(crate) fn assert_invariants(&self) {
let Some(visible_root) = self.forest.first() else {
let Some(visible_root) = self.nodes.first() else {
assert!(self.backlog.is_empty());
assert!(self.id_to_key.is_empty());
return;
Expand All @@ -756,9 +765,9 @@ mod invariants {
.copied()
.expect("tree not empty");

assert_eq!(max_node_key.into_usize() + 1, self.forest.len());
assert_eq!(max_node_key.into_usize() + 1, self.nodes.len());

for node_key in (0..self.forest.len()).map(NodeKey::new) {
for node_key in (0..self.nodes.len()).map(NodeKey::new) {
self.assert_node_invariants(node_key);
}

Expand All @@ -782,7 +791,7 @@ mod invariants {
// If the node is not the root of its subtree, make sure its parent
// is valid.
if node.parent_key.is_some() {
assert!(node.parent_key.into_usize() < self.forest.len());
assert!(node.parent_key.into_usize() < self.nodes.len());
}

// Make sure the node's children are valid.
Expand All @@ -794,7 +803,7 @@ mod invariants {
);

assert!(
child_key.into_usize() < self.forest.len(),
child_key.into_usize() < self.nodes.len(),
"the node at {node_key:?} has a child with an oob key: \
{child_key:?}"
);
Expand Down Expand Up @@ -913,7 +922,7 @@ mod debug {
}

fn iter(&self) -> impl Iterator<Item = (&Node, NodeKey)> {
self.forest
self.nodes
.iter()
.enumerate()
.map(|(idx, node)| (node, NodeKey::new(idx)))
Expand Down Expand Up @@ -1010,7 +1019,7 @@ mod encode {
impl Encode for Forest {
#[inline]
fn encode(&self, buf: &mut impl Buffer) {
self.forest.encode(buf);
self.nodes.encode(buf);
self.backlog.encode(buf);
self.id_to_key.encode(buf);
}
Expand All @@ -1024,7 +1033,7 @@ mod encode {
let forest = Vec::decode(buf)?;
let backlog = Backlog::decode(buf)?;
let id_to_key = FxHashMap::decode(buf)?;
Ok(Self { forest, backlog, id_to_key })
Ok(Self { nodes: forest, backlog, id_to_key })
}
}

Expand Down
Loading
Loading