gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 684HTML 640Markdown 12TOML 62286 Definitions

Backport big tree benchmarks to 0.1

59c2af6eba7f332c359d8c7226163265d56fe725 · by Nico Burns · (3 years ago) · parent 8936790d6 · browse the tree at this commit

Semantic delta · 11

src/lib.rs

src/randomizable.rs

4 files · +280 −0

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