diff --git a/Cargo.toml b/Cargo.toml
index 0b0aaac64..c95e96c1b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ hash32-derive = "0.1"
num-traits = { version = "0.2", default-features = false }
typenum = "1"
hashbrown = { version = "0.12", optional = true }
+rand = { version = "0.8.5", optional = true }
serde = { version = "1.0", optional = true, features = ["serde_derive"] }
[features]
@@ -27,9 +28,15 @@ default = ["std"]
alloc = ["hashbrown"]
std = ["num-traits/std"]
serde = ["dep:serde"]
+random = ["dep:rand"]
[dev-dependencies]
criterion = "0.3"
+rand = "0.8.5"
+rand_chacha = "0.3.1"
+# We need to explicitly set a version here so that cargo deny doesn't complain
+# Don't forget to update this when bumping the version of taffy!
+taffy = { path = ".", version = "0.1.0", features = ["random"] }
[profile.release]
lto = true
@@ -40,6 +47,11 @@ name = "generated"
path = "benches/generated/mod.rs"
harness = false
+[[bench]]
+name = "big_tree"
+path = "benches/big_tree.rs"
+harness = false
+
[[bench]]
name = "complex"
path = "benches/complex.rs"
diff --git a/benches/big_tree.rs b/benches/big_tree.rs
new file mode 100644
index 000000000..d40266ded
--- /dev/null
+++ b/benches/big_tree.rs
@@ -0,0 +1,210 @@
+//! This file includes benchmarks for very large, pseudo-randomly generated trees
+use criterion::{criterion_group, criterion_main, Criterion};
+use rand::prelude::*;
+use rand_chacha::ChaCha8Rng;
+use taffy::prelude::*;
+use taffy::randomizable::Randomizeable;
+use taffy::style::Style;
+
+/// Build a random leaf node
+fn build_random_leaf(taffy: &mut Taffy, rng: &mut ChaCha8Rng) -> Node {
+ taffy.new_node(Style::random(rng), &[]).unwrap()
+}
+
+/// A tree with many children that have shallow depth
+fn build_flat_hierarchy(taffy: &mut Taffy, total_node_count: u32) -> Node {
+ let mut rng = ChaCha8Rng::seed_from_u64(12345);
+ let mut children = Vec::new();
+ let mut node_count = 0;
+
+ while node_count < total_node_count {
+ let sub_children_count = rng.gen_range(1..=4);
+ let sub_children: Vec<Node> = (0..sub_children_count).map(|_| build_random_leaf(taffy, &mut rng)).collect();
+ let node = taffy.new_node(Style::random(&mut rng), &sub_children).unwrap();
+
+ children.push(node);
+ node_count += 1 + sub_children_count;
+ }
+
+ taffy.new_node(Style { ..Default::default() }, children.as_slice()).unwrap()
+}
+
+/// A helper function to recursively construct a deep tree
+fn build_deep_tree(
+ taffy: &mut Taffy,
+ max_nodes: u32,
+ branching_factor: u32,
+ create_leaf_node: &mut impl FnMut(&mut Taffy) -> Node,
+ create_flex_node: &mut impl FnMut(&mut Taffy, Vec<Node>) -> Node,
+) -> Vec<Node> {
+ if max_nodes <= branching_factor {
+ // Build leaf nodes
+ return (0..max_nodes).map(|_| create_leaf_node(taffy)).collect();
+ }
+
+ // Add another layer to the tree
+ // Each child gets an equal amount of the remaining nodes
+ (0..branching_factor)
+ .map(|_| {
+ let max_nodes = (max_nodes - branching_factor) / branching_factor;
+ let sub_children = build_deep_tree(taffy, max_nodes, branching_factor, create_leaf_node, create_flex_node);
+ create_flex_node(taffy, sub_children)
+ })
+ .collect()
+}
+
+/// A tree with a higher depth for a more realistic scenario
+fn build_deep_hierarchy(taffy: &mut Taffy, node_count: u32, branching_factor: u32) -> Node {
+ let mut rng = ChaCha8Rng::seed_from_u64(12345);
+ let mut build_leaf_node = |taffy: &mut Taffy| build_random_leaf(taffy, &mut rng);
+ let mut rng = ChaCha8Rng::seed_from_u64(12345);
+ let mut build_flex_node = |taffy: &mut Taffy, children: Vec<Node>| {
+ taffy.new_node(Style::random(&mut rng), &children).unwrap()
+ };
+
+ let tree = build_deep_tree(taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
+
+ taffy.new_node(Style { ..Default::default() }, &tree).unwrap()
+}
+
+/// A deep tree that matches the shape and styling that yoga use on their benchmarks
+fn build_yoga_deep_hierarchy(taffy: &mut Taffy, node_count: u32, branching_factor: u32) -> Node {
+ let style = Style {
+ size: Size { width: Dimension::Points(10.0), height: Dimension::Points(10.0) },
+ flex_grow: 1.0,
+ ..Default::default()
+ };
+ let mut build_leaf_node = |taffy: &mut Taffy| taffy.new_node(style.clone(), &[]).unwrap();
+ let mut build_flex_node =
+ |taffy: &mut Taffy, children: Vec<Node>| taffy.new_node(style.clone(), &children).unwrap();
+
+ let tree = build_deep_tree(taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
+ let root = taffy.new_node(Style { ..Default::default() }, &tree).unwrap();
+
+ root
+}
+
+fn taffy_benchmarks(c: &mut Criterion) {
+ let mut group = c.benchmark_group("yoga benchmarks");
+ group.sample_size(10);
+
+ group.bench_function("10 nodes", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_yoga_deep_hierarchy(&mut taffy, 10, 10);
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("100 nodes", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_yoga_deep_hierarchy(&mut taffy, 100, 10);
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("1_000 nodes", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_yoga_deep_hierarchy(&mut taffy, 1_000, 10);
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("10_000 nodes", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_yoga_deep_hierarchy(&mut taffy, 10_000, 10);
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("100_000 nodes", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_yoga_deep_hierarchy(&mut taffy, 100_000, 10);
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ // group.bench_function("1_000_000 nodes", |b| {
+ // let mut taffy = Taffy::new();
+ // let root = build_yoga_deep_hierarchy(&mut taffy, 1_000_000, 10);
+ // b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ // });
+ drop(group);
+
+ // Decrease sample size, because the tasks take longer
+ let mut group = c.benchmark_group("big trees (wide)");
+ group.sample_size(10);
+
+ group.bench_function("10 nodes (2-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_flat_hierarchy(&mut taffy, 10);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("100 nodes (2-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_flat_hierarchy(&mut taffy, 100);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("1_000 nodes (2-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_flat_hierarchy(&mut taffy, 1_000);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("10_000 nodes (2-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_flat_hierarchy(&mut taffy, 10_000);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("100_000 nodes (2-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_flat_hierarchy(&mut taffy, 100_000);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("100_000 nodes (7-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_deep_hierarchy(&mut taffy, 100_000, 7);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ drop(group);
+
+ // Decrease sample size, because the tasks take longer
+ let mut group = c.benchmark_group("big trees (deep)");
+ group.sample_size(10);
+
+ group.bench_function("4000 nodes (12-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_deep_hierarchy(&mut taffy, 4000, 2);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ group.bench_function("10_000 nodes (14-level hierarchy)", |b| {
+ let mut taffy = Taffy::new();
+ let root = build_deep_hierarchy(&mut taffy, 10_000, 2);
+
+ b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ });
+
+ // group.bench_function("100_000 nodes (17-level hierarchy)", |b| {
+ // let mut taffy = Taffy::new();
+ // let root = build_deep_hierarchy(&mut taffy, 100_000, 2);
+
+ // b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ // });
+
+ // group.bench_function("1_000_000 nodes (20-level hierarchy)", |b| {
+ // let mut taffy = Taffy::new();
+ // let root = build_deep_hierarchy(&mut taffy, 1_000_000, 2);
+
+ // b.iter(|| taffy.compute_layout(root, Size::undefined()).unwrap())
+ // });
+}
+
+criterion_group!(benches, taffy_benchmarks);
+criterion_main!(benches);
diff --git a/src/lib.rs b/src/lib.rs
index 074a1dd07..31ecf1998 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,6 +26,9 @@ mod sys;
pub use crate::node::Taffy;
+#[cfg(feature = "random")]
+pub mod randomizable;
+
#[cfg(feature = "std")]
use core::fmt::{Display, Formatter, Result};
diff --git a/src/randomizable.rs b/src/randomizable.rs
new file mode 100644
index 000000000..bcdf5e826
--- /dev/null
+++ b/src/randomizable.rs
@@ -0,0 +1,55 @@
+//! Pseudo-random generation of data (e.g. UI nodes) to use in benchmarks.
+
+use rand::Rng;
+
+use crate::geometry::Size;
+use crate::style::Dimension;
+use crate::style::Style;
+
+/// A trait for generating pseudo-random instances.
+pub trait Randomizeable {
+ /// Generate a pseudo-random instance of this type.
+ ///
+ /// This can be useful for benchmarking.
+ fn random<R>(rng: &mut R) -> Self
+ where
+ R: Rng;
+}
+
+impl Randomizeable for Dimension {
+ fn random<R>(rng: &mut R) -> Self
+ where
+ R: Rng,
+ {
+ let switch: f32 = rng.gen_range(0.0..=1.0);
+
+ if switch < 0.2 {
+ Dimension::Auto
+ } else if switch < 0.4 {
+ Dimension::Undefined
+ } else if switch < 0.8 {
+ Dimension::Points(rng.gen_range(0.0..500.0))
+ } else {
+ Dimension::Percent(rng.gen_range(0.0..1.0))
+ }
+ }
+}
+
+impl Randomizeable for Size<Dimension> {
+ fn random<R>(rng: &mut R) -> Self
+ where
+ R: Rng,
+ {
+ Size { width: Dimension::random(rng), height: Dimension::random(rng) }
+ }
+}
+
+impl Randomizeable for Style {
+ fn random<R>(rng: &mut R) -> Self
+ where
+ R: Rng,
+ {
+ // TODO: Add more attributes
+ Style { size: Size::<Dimension>::random(rng), ..Default::default() }
+ }
+}
\ No newline at end of file