tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definiciones

Refactor benchmarks (#472)

* Refactor benchmarks:

- Abstract tree creation behind a trait
- Implement trait for Yoga and Taffy

* Remove unused random_style module

* Set super deep benchmarks back to branching factor of 1

* Feature flag small and large benchmarks

* Split benchmark definitions into functions

* Add deep(auto) benchmarks

* Improve super-deep hierarchy benchmarks

6031fa21b5800de29385e8426daa4f4929754601 · por Nico Burns · (hace 3 años) · padre 9e62e959c · ver el árbol en este commit

Delta semántico · 72

benches/benches/tree_creation.rs

benches/src/lib.rs

benches/src/random_style.rs

benches/src/taffy_helpers.rs

benches/src/yoga_helpers.rs

src/tree/taffy_tree/tree.rs

8 archivos · +446 −237

diff --git a/benches/Cargo.toml b/benches/Cargo.toml
index d270b1326..b83939ce5 100644
--- a/benches/Cargo.toml
+++ b/benches/Cargo.toml
@@ -23,6 +23,9 @@ slotmap = { version = "1.0.6", optional = true }
 
 [features]
 yoga = ["dep:yoga", "dep:slotmap", "dep:ordered-float"]
+yoga-super-deep = ["yoga"]
+small = []
+large = []
 
 [[bench]]
 name = "tree_creation"
diff --git a/benches/benches/flexbox.rs b/benches/benches/flexbox.rs
index 393bb996e..a2c532149 100644
--- a/benches/benches/flexbox.rs
+++ b/benches/benches/flexbox.rs
@@ -1,155 +1,90 @@
 //! This file includes benchmarks for very large, pseudo-randomly generated trees
 use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
-use rand::prelude::*;
-use rand_chacha::ChaCha8Rng;
+use rand::Rng;
 use taffy::prelude::*;
-use taffy::style::Style;
-use taffy_benchmarks::{build_deep_tree, Randomizeable};
+use taffy::style::Dimension;
+use taffy::style::Style as TaffyStyle;
+
+use taffy_benchmarks::{BuildTreeExt, FixedStyleGenerator, GenStyle, TaffyTreeBuilder};
 
-#[cfg(feature = "yoga")]
-use slotmap::SlotMap;
 #[cfg(feature = "yoga")]
 use taffy_benchmarks::yoga_helpers;
 #[cfg(feature = "yoga")]
-use yoga_helpers::yg;
-
-/// Build a random leaf node
-fn build_random_leaf(taffy: &mut Taffy, rng: &mut ChaCha8Rng) -> NodeId {
-    taffy.new_with_children(Style::random(rng), &[]).unwrap()
-}
+use yoga_helpers::{yg, YogaTreeBuilder};
 
-/// A tree with many children that have shallow depth
-fn build_taffy_flat_hierarchy(total_node_count: u32) -> (Taffy, NodeId) {
-    let mut taffy = Taffy::new();
-    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<NodeId> =
-            (0..sub_children_count).map(|_| build_random_leaf(&mut taffy, &mut rng)).collect();
-        let node = taffy.new_with_children(Style::random(&mut rng), &sub_children).unwrap();
-
-        children.push(node);
-        node_count += 1 + sub_children_count;
+fn random_dimension(rng: &mut impl Rng) -> Dimension {
+    match rng.gen_range(0.0..=1.0) {
+        rand if rand < 0.2 => Dimension::Auto,
+        rand if rand < 0.8 => Dimension::Length(rng.gen_range(0.0..500.0)),
+        _ => Dimension::Percent(rng.gen_range(0.0..1.0)),
     }
-
-    let root = taffy.new_with_children(Style::DEFAULT, children.as_slice()).unwrap();
-    (taffy, root)
 }
-
-#[cfg(feature = "yoga")]
-/// A tree with many children that have shallow depth
-fn build_yoga_flat_hierarchy(total_node_count: u32) -> (yg::YogaTree, yg::NodeId) {
-    let mut tree = SlotMap::new();
-    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<yg::NodeId> = (0..sub_children_count)
-            .map(|_| yoga_helpers::new_with_children(&mut tree, &Style::random(&mut rng), vec![]))
-            .collect();
-        let node = yoga_helpers::new_with_children(&mut tree, &Style::random(&mut rng), sub_children);
-
-        children.push(node);
-        node_count += 1 + sub_children_count;
+pub struct RandomStyleGenerator;
+impl GenStyle<TaffyStyle> for RandomStyleGenerator {
+    fn create_leaf_style(&mut self, rng: &mut impl Rng) -> TaffyStyle {
+        TaffyStyle { size: Size { width: random_dimension(rng), height: random_dimension(rng) }, ..Default::default() }
+    }
+    fn create_container_style(&mut self, rng: &mut impl Rng) -> TaffyStyle {
+        TaffyStyle { size: Size { width: random_dimension(rng), height: random_dimension(rng) }, ..Default::default() }
     }
-
-    let root = yoga_helpers::new_with_children(&mut tree, &Style::DEFAULT, children);
-    (tree, root)
 }
 
-/// A tree with a higher depth for a more realistic scenario
-fn build_taffy_deep_hierarchy(node_count: u32, branching_factor: u32) -> (Taffy, NodeId) {
-    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<NodeId>| taffy.new_with_children(Style::random(&mut rng), &children).unwrap();
-
-    let mut taffy = Taffy::new();
-    let tree = build_deep_tree(&mut taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
-    let root = taffy.new_with_children(Style::DEFAULT, &tree).unwrap();
-    (taffy, root)
+/// A deep tree that matches the shape and styling that yoga use on their benchmarks
+fn build_flat_hierarchy<G: GenStyle<TaffyStyle>, TreeBuilder: BuildTreeExt<G>>(
+    target_node_count: u32,
+    style_generator: impl FnOnce() -> G,
+) -> (TreeBuilder::Tree, TreeBuilder::Node) {
+    let tree_builder = TreeBuilder::new(style_generator());
+    tree_builder.build_flat_hierarchy(target_node_count)
 }
 
-#[cfg(feature = "yoga")]
-/// A tree with a higher depth for a more realistic scenario
-fn build_yoga_deep_hierarchy(node_count: u32, branching_factor: u32) -> (yg::YogaTree, yg::NodeId) {
-    let mut rng = ChaCha8Rng::seed_from_u64(12345);
-    let mut build_leaf_node =
-        |tree: &mut yg::YogaTree| yoga_helpers::new_with_children(tree, &Style::random(&mut rng), vec![]);
-    let mut rng = ChaCha8Rng::seed_from_u64(12345);
-    let mut build_flex_node = |tree: &mut yg::YogaTree, children: Vec<yg::NodeId>| {
-        yoga_helpers::new_with_children(tree, &Style::random(&mut rng), children)
-    };
-
-    let mut tree = SlotMap::new();
-    let children = build_deep_tree(&mut tree, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
-    let root = yoga_helpers::new_with_children(&mut tree, &Style::DEFAULT, children);
-
-    (tree, root)
+/// A deep tree that matches the shape and styling that yoga use on their benchmarks
+fn build_deep_hierarchy<G: GenStyle<TaffyStyle>, TreeBuilder: BuildTreeExt<G>>(
+    node_count: u32,
+    branching_factor: u32,
+    style_generator: impl FnOnce() -> G,
+) -> (TreeBuilder::Tree, TreeBuilder::Node) {
+    let tree_builder = TreeBuilder::new(style_generator());
+    tree_builder.build_deep_hierarchy(node_count, branching_factor)
 }
 
 /// A deep tree that matches the shape and styling that yoga use on their benchmarks
-fn build_taffy_huge_nested_hierarchy(node_count: u32, branching_factor: u32) -> (Taffy, NodeId) {
-    let style = Style {
-        size: Size { width: Dimension::Length(10.0), height: Dimension::Length(10.0) },
-        flex_grow: 1.0,
-        ..Default::default()
-    };
-    let mut build_leaf_node = |taffy: &mut Taffy| taffy.new_leaf(style.clone()).unwrap();
-    let mut build_flex_node =
-        |taffy: &mut Taffy, children: Vec<NodeId>| taffy.new_with_children(style.clone(), &children).unwrap();
-
-    let mut taffy = Taffy::new();
-    let tree = build_deep_tree(&mut taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
-    let root = taffy.new_with_children(Style::DEFAULT, &tree).unwrap();
-    (taffy, root)
+fn build_super_deep_hierarchy<G: GenStyle<TaffyStyle>, TreeBuilder: BuildTreeExt<G>>(
+    depth: u32,
+    nodes_per_level: u32,
+    style_generator: impl FnOnce() -> G,
+) -> (TreeBuilder::Tree, TreeBuilder::Node) {
+    let tree_builder = TreeBuilder::new(style_generator());
+    tree_builder.build_super_deep_hierarchy(depth, nodes_per_level)
 }
 
-#[cfg(feature = "yoga")]
 /// A deep tree that matches the shape and styling that yoga use on their benchmarks
-fn build_yoga_huge_nested_hierarchy(node_count: u32, branching_factor: u32) -> (yg::YogaTree, yg::NodeId) {
-    let style = Style {
-        size: Size { width: Dimension::Length(10.0), height: Dimension::Length(10.0) },
-        flex_grow: 1.0,
-        ..Default::default()
-    };
-    let mut build_leaf_node = |tree: &mut yg::YogaTree| -> yg::NodeId {
-        let mut node = yg::Node::new();
-        yoga_helpers::apply_taffy_style(&mut node, &style.clone());
-        tree.insert(node)
-    };
-    let mut build_flex_node = |tree: &mut yg::YogaTree, children: Vec<yg::NodeId>| -> yg::NodeId {
-        let mut node = yg::Node::new();
-        yoga_helpers::apply_taffy_style(&mut node, &style.clone());
-        for (i, child) in children.into_iter().enumerate() {
-            node.insert_child(&mut tree[child], i as u32);
-        }
-        tree.insert(node)
-    };
-
-    let mut tree = SlotMap::new();
-    let children = build_deep_tree(&mut tree, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
-    let mut root = yg::Node::new();
-    for (i, child) in children.into_iter().enumerate() {
-        root.insert_child(&mut tree[child], i as u32);
-    }
-    let root = tree.insert(root);
-    (tree, root)
+fn build_huge_nested_hierarchy<G: GenStyle<TaffyStyle>, TreeBuilder: BuildTreeExt<G>>(
+    node_count: u32,
+    branching_factor: u32,
+    style_generator: impl FnOnce() -> G,
+) -> (TreeBuilder::Tree, TreeBuilder::Node) {
+    let tree_builder = TreeBuilder::new(style_generator());
+    tree_builder.build_deep_hierarchy(node_count, branching_factor)
 }
 
-fn taffy_benchmarks(c: &mut Criterion) {
+fn huge_nested_benchmarks(c: &mut Criterion) {
     let mut group = c.benchmark_group("yoga 'huge nested'");
-    for node_count in [1_000u32, 10_000, 100_000].iter() {
+    let style = Style { size: length(10.0), flex_grow: 1.0, ..Default::default() };
+    let style_gen = || FixedStyleGenerator(style.clone());
+    for node_count in [
+        #[cfg(feature = "small")]
+        1_000u32,
+        10_000,
+        #[cfg(feature = "large")]
+        100_000,
+    ]
+    .iter()
+    {
         #[cfg(feature = "yoga")]
         group.bench_with_input(BenchmarkId::new("Yoga", node_count), node_count, |b, &node_count| {
             b.iter_batched(
-                || build_yoga_huge_nested_hierarchy(node_count, 10),
+                || build_huge_nested_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, 10, style_gen),
                 |(mut tree, root)| {
                     tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR);
                 },
@@ -158,24 +93,34 @@ fn taffy_benchmarks(c: &mut Criterion) {
         });
         group.bench_with_input(BenchmarkId::new("Taffy", node_count), node_count, |b, &node_count| {
             b.iter_batched(
-                || build_taffy_huge_nested_hierarchy(node_count, 10),
+                || build_huge_nested_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, 10, style_gen),
                 |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
                 criterion::BatchSize::SmallInput,
             )
         });
     }
     group.finish();
+}
 
+fn wide_benchmarks(c: &mut Criterion) {
     // Decrease sample size, because the tasks take longer
-    let mut group = c.benchmark_group("big trees (wide)");
+    let mut group = c.benchmark_group("Wide tree");
     group.sample_size(10);
-    for node_count in [1_000u32, 10_000, 100_000].iter() {
+    for node_count in [
+        #[cfg(feature = "small")]
+        1_000u32,
+        10_000,
+        #[cfg(feature = "large")]
+        100_000,
+    ]
+    .iter()
+    {
         #[cfg(feature = "yoga")]
         let benchmark_id = BenchmarkId::new(format!("Yoga (2-level hierarchy)"), node_count);
         #[cfg(feature = "yoga")]
         group.bench_with_input(benchmark_id, node_count, |b, &node_count| {
             b.iter_batched(
-                || build_yoga_flat_hierarchy(node_count),
+                || build_flat_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, || RandomStyleGenerator),
                 |(mut tree, root)| {
                     tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR);
                 },
@@ -185,23 +130,30 @@ fn taffy_benchmarks(c: &mut Criterion) {
         let benchmark_id = BenchmarkId::new(format!("Taffy (2-level hierarchy)"), node_count);
         group.bench_with_input(benchmark_id, node_count, |b, &node_count| {
             b.iter_batched(
-                || build_taffy_flat_hierarchy(node_count),
+                || build_flat_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, || RandomStyleGenerator),
                 |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
                 criterion::BatchSize::SmallInput,
             )
         });
     }
     group.finish();
+}
 
+fn deep_random_benchmarks(c: &mut Criterion) {
     // Decrease sample size, because the tasks take longer
-    let mut group = c.benchmark_group("big trees (deep)");
+    let mut group = c.benchmark_group("Deep tree (random size)");
     group.sample_size(10);
-    let benches = [(4000, "(12-level hierarchy)"), (10_000, "(14-level hierarchy)"), (100_000, "(17-level hierarchy)")];
+    let benches = [
+        (4000, "(12-level hierarchy)"),
+        (10_000, "(14-level hierarchy)"),
+        #[cfg(feature = "large")]
+        (100_000, "(17-level hierarchy)"),
+    ];
     for (node_count, label) in benches.iter() {
         #[cfg(feature = "yoga")]
         group.bench_with_input(BenchmarkId::new(format!("Yoga {label}"), node_count), node_count, |b, &node_count| {
             b.iter_batched(
-                || build_yoga_deep_hierarchy(node_count, 2),
+                || build_deep_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, 2, || RandomStyleGenerator),
                 |(mut tree, root)| {
                     tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR);
                 },
@@ -210,30 +162,41 @@ fn taffy_benchmarks(c: &mut Criterion) {
         });
         group.bench_with_input(BenchmarkId::new(format!("Taffy {label}"), node_count), node_count, |b, &node_count| {
             b.iter_batched(
-                || build_taffy_deep_hierarchy(node_count, 2),
+                || build_deep_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, 2, || RandomStyleGenerator),
                 |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
                 criterion::BatchSize::SmallInput,
             )
         });
     }
     group.finish();
+}
 
-    let mut group = c.benchmark_group("super deep (1000-level hierarchy)");
+fn deep_auto_benchmarks(c: &mut Criterion) {
+    // Decrease sample size, because the tasks take longer
+    let mut group = c.benchmark_group("Deep tree (auto size)");
     group.sample_size(10);
-    for node_count in [1000u32].iter() {
+    let style = Style { flex_grow: 1.0, margin: length(10.0), ..Default::default() };
+    let style_gen = || FixedStyleGenerator(style.clone());
+    let benches = [
+        (4000, "(12-level hierarchy)"),
+        (10_000, "(14-level hierarchy)"),
+        #[cfg(feature = "large")]
+        (100_000, "(17-level hierarchy)"),
+    ];
+    for (node_count, label) in benches.iter() {
         #[cfg(feature = "yoga")]
-        group.bench_with_input(BenchmarkId::new("Yoga", node_count), node_count, |b, &node_count| {
+        group.bench_with_input(BenchmarkId::new(format!("Yoga {label}"), node_count), node_count, |b, &node_count| {
             b.iter_batched(
-                || build_yoga_deep_hierarchy(node_count, 2),
+                || build_deep_hierarchy::<_, YogaTreeBuilder<_, _>>(node_count, 2, style_gen),
                 |(mut tree, root)| {
                     tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR);
                 },
                 criterion::BatchSize::SmallInput,
             )
         });
-        group.bench_with_input(BenchmarkId::new("Taffy", node_count), node_count, |b, &node_count| {
+        group.bench_with_input(BenchmarkId::new(format!("Taffy {label}"), node_count), node_count, |b, &node_count| {
             b.iter_batched(
-                || build_taffy_deep_hierarchy(node_count, 2),
+                || build_deep_hierarchy::<_, TaffyTreeBuilder<_, _>>(node_count, 2, style_gen),
                 |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
                 criterion::BatchSize::SmallInput,
             )
@@ -242,5 +205,65 @@ fn taffy_benchmarks(c: &mut Criterion) {
     group.finish();
 }
 
+fn super_deep_benchmarks(c: &mut Criterion) {
+    let mut group = c.benchmark_group("super deep");
+    group.sample_size(10);
+    struct SuperDeepStyleGen;
+    impl GenStyle<TaffyStyle> for SuperDeepStyleGen {
+        fn create_leaf_style(&mut self, _rng: &mut impl Rng) -> TaffyStyle {
+            // let flex_direction = if rng.gen::<f32>() < 0.5 { FlexDirection::Column } else { FlexDirection::Row };
+            let flex_direction = FlexDirection::Row;
+            Style { flex_direction, flex_grow: 1.0, margin: length(10.0), ..Default::default() }
+        }
+        fn create_container_style(&mut self, rng: &mut impl Rng) -> TaffyStyle {
+            self.create_leaf_style(rng)
+        }
+    }
+    for depth in [
+        #[cfg(feature = "small")]
+        50u32,
+        100,
+        #[cfg(feature = "large")]
+        200,
+    ]
+    .iter()
+    {
+        // Yoga is particularly slow at these benchmarks, so we gate them behind a separate feature flag
+        #[cfg(all(feature = "yoga", feature = "yoga-super-deep"))]
+        group.bench_with_input(BenchmarkId::new("Yoga", depth), depth, |b, &depth| {
+            b.iter_batched(
+                || build_super_deep_hierarchy::<_, YogaTreeBuilder<_, _>>(depth, 3, || SuperDeepStyleGen),
+                |(mut tree, root)| {
+                    tree[root].calculate_layout(800., 800., yg::Direction::LTR);
+                },
+                criterion::BatchSize::SmallInput,
+            )
+        });
+        group.bench_with_input(BenchmarkId::new("Taffy", depth), depth, |b, &depth| {
+            b.iter_batched(
+                || build_super_deep_hierarchy::<_, TaffyTreeBuilder<_, _>>(depth, 3, || SuperDeepStyleGen),
+                |(mut taffy, root)| {
+                    taffy
+                        .compute_layout(
+                            root,
+                            Size { width: AvailableSpace::Definite(800.), height: AvailableSpace::Definite(800.) },
+                        )
+                        .unwrap()
+                },
+                criterion::BatchSize::SmallInput,
+            )
+        });
+    }
+    group.finish();
+}
+
+fn taffy_benchmarks(c: &mut Criterion) {
+    huge_nested_benchmarks(c);
+    wide_benchmarks(c);
+    deep_auto_benchmarks(c);
+    deep_random_benchmarks(c);
+    super_deep_benchmarks(c);
+}
+
 criterion_group!(benches, taffy_benchmarks);
 criterion_main!(benches);
diff --git a/benches/benches/tree_creation.rs b/benches/benches/tree_creation.rs
index 0c2178d50..6f0c83dd1 100644
--- a/benches/benches/tree_creation.rs
+++ b/benches/benches/tree_creation.rs
@@ -48,14 +48,14 @@ fn build_yoga_flat_hierarchy(total_node_count: u32) -> (yg::YogaTree, yg::NodeId
     while node_count < total_node_count {
         let sub_children_count = rng.gen_range(1..=4);
         let sub_children: Vec<yg::NodeId> =
-            (0..sub_children_count).map(|_| yoga_helpers::new_default_style_with_children(&mut tree, vec![])).collect();
-        let node = yoga_helpers::new_default_style_with_children(&mut tree, sub_children);
+            (0..sub_children_count).map(|_| yoga_helpers::new_default_style_with_children(&mut tree, &[])).collect();
+        let node = yoga_helpers::new_default_style_with_children(&mut tree, &sub_children);
 
         children.push(node);
         node_count += 1 + sub_children_count;
     }
 
-    let root = yoga_helpers::new_default_style_with_children(&mut tree, children);
+    let root = yoga_helpers::new_default_style_with_children(&mut tree, &children);
     (tree, root)
 }
 
diff --git a/benches/src/lib.rs b/benches/src/lib.rs
index 42c5a4662..6d67c6774 100644
--- a/benches/src/lib.rs
+++ b/benches/src/lib.rs
@@ -1,31 +1,127 @@
+// As each benchmark suite is compiled as a seperate crate and uses different helpers, we end up with a bunch
+// of false positives for this lint. So let's just disable it for this code.
+#![allow(dead_code)]
+
+pub mod taffy_helpers;
+pub use taffy_helpers::TaffyTreeBuilder;
+
 #[cfg(feature = "yoga")]
 pub mod yoga_helpers;
+#[cfg(feature = "yoga")]
+pub use yoga_helpers::YogaTreeBuilder;
+
+use rand::distributions::uniform::SampleRange;
+use rand::{Rng, SeedableRng};
+use rand_chacha::ChaCha8Rng;
+use taffy::style::Style as TaffyStyle;
+
+pub const STANDARD_RNG_SEED: u64 = 12345;
+
+pub trait GenStyle<Style: Default> {
+    fn create_leaf_style(&mut self, rng: &mut impl Rng) -> Style;
+    fn create_container_style(&mut self, rng: &mut impl Rng) -> Style;
+    fn create_root_style(&mut self, _rng: &mut impl Rng) -> Style {
+        Default::default()
+    }
+}
+
+pub struct FixedStyleGenerator(pub TaffyStyle);
+impl GenStyle<TaffyStyle> for FixedStyleGenerator {
+    fn create_leaf_style(&mut self, _rng: &mut impl Rng) -> TaffyStyle {
+        self.0.clone()
+    }
+    fn create_container_style(&mut self, _rng: &mut impl Rng) -> TaffyStyle {
+        self.0.clone()
+    }
+}
+
+pub trait BuildTree<R: Rng, G: GenStyle<TaffyStyle>>: Sized {
+    type Tree;
+    type Node: Clone;
+
+    fn with_rng(rng: R, style_generator: G) -> Self;
+
+    fn random_usize(&mut self, range: impl SampleRange<usize>) -> usize;
+    fn create_leaf_node(&mut self) -> Self::Node;
+    fn create_container_node(&mut self, children: &[Self::Node]) -> Self::Node;
+    fn set_root_children(&mut self, children: &[Self::Node]);
+    fn total_node_count(&mut self) -> usize;
+    fn into_tree_and_root(self) -> (Self::Tree, Self::Node);
+
+    fn build_n_leaf_nodes(&mut self, n: usize) -> Vec<Self::Node> {
+        (0..n).map(|_| self.create_leaf_node()).collect()
+    }
 
-mod random_style;
-pub use random_style::Randomizeable;
-
-/// A helper function to recursively construct a deep tree
-#[allow(dead_code)]
-pub fn build_deep_tree<T, N>(
-    tree: &mut T,
-    max_nodes: u32,
-    branching_factor: u32,
-    create_leaf_node: &mut impl FnMut(&mut T) -> N,
-    create_container_node: &mut impl FnMut(&mut T, Vec<N>) -> N,
-) -> Vec<N> {
-    if max_nodes <= branching_factor {
-        // Build leaf nodes
-        return (0..max_nodes).map(|_| create_leaf_node(tree)).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(tree, max_nodes, branching_factor, create_leaf_node, create_container_node);
-            create_container_node(tree, sub_children)
-        })
-        .collect()
+    /// A helper function to recursively construct a deep tree
+    fn build_deep_tree(&mut self, max_nodes: u32, branching_factor: u32) -> Vec<Self::Node> {
+        if max_nodes <= branching_factor {
+            // Build leaf nodes
+            return (0..max_nodes).map(|_| self.create_leaf_node()).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 children = self.build_deep_tree(max_nodes, branching_factor);
+                self.create_container_node(&children)
+            })
+            .collect()
+    }
+
+    /// A helper function to recursively construct a deep tree
+    fn build_super_deep_hierarchy(mut self, depth: u32, nodes_per_level: u32) -> (Self::Tree, Self::Node) {
+        let mut children = Vec::with_capacity(nodes_per_level as usize);
+        for _ in 0..depth {
+            let node_with_children = self.create_container_node(&children);
+
+            children.clear();
+            children.push(node_with_children);
+            for _ in 0..(nodes_per_level - 1) {
+                children.push(self.create_leaf_node())
+            }
+        }
+        self.set_root_children(&children);
+        self.into_tree_and_root()
+    }
+
+    /// A tree with a higher depth for a more realistic scenario
+    fn build_deep_hierarchy(mut self, node_count: u32, branching_factor: u32) -> (Self::Tree, Self::Node) {
+        let children = self.build_deep_tree(node_count, branching_factor);
+        self.set_root_children(&children);
+        self.into_tree_and_root()
+    }
+
+    /// A tree with many children that have shallow depth
+    fn build_flat_hierarchy(mut self, target_node_count: u32) -> (Self::Tree, Self::Node) {
+        let mut children = Vec::new();
+
+        while self.total_node_count() < target_node_count as usize {
+            let count = self.random_usize(1..=4);
+            let sub_children = self.build_n_leaf_nodes(count);
+            let node = self.create_container_node(&sub_children);
+            children.push(node);
+        }
+
+        self.set_root_children(&children);
+        self.into_tree_and_root()
+    }
+}
+
+pub trait BuildTreeExt<G: GenStyle<TaffyStyle>>: BuildTree<ChaCha8Rng, G> {
+    fn with_seed(seed: u64, style_generator: G) -> Self
+    where
+        Self: Sized,
+    {
+        let rng = ChaCha8Rng::seed_from_u64(seed);
+        Self::with_rng(rng, style_generator)
+    }
+
+    fn new(style_generator: G) -> Self
+    where
+        Self: Sized,
+    {
+        Self::with_seed(STANDARD_RNG_SEED, style_generator)
+    }
 }
diff --git a/benches/src/random_style.rs b/benches/src/random_style.rs
deleted file mode 100644
index f7ef6154c..000000000
--- a/benches/src/random_style.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-//! Pseudo-random generation of data (e.g. UI nodes) to use in benchmarks.
-
-use rand::Rng;
-
-use taffy::geometry::Size;
-use taffy::style::Dimension;
-use taffy::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.8 {
-            Dimension::Length(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() }
-    }
-}
diff --git a/benches/src/taffy_helpers.rs b/benches/src/taffy_helpers.rs
new file mode 100644
index 000000000..e0fbb6b2a
--- /dev/null
+++ b/benches/src/taffy_helpers.rs
@@ -0,0 +1,55 @@
+use rand::distributions::uniform::SampleRange;
+use rand::Rng;
+use rand_chacha::ChaCha8Rng;
+use taffy::style::Style as TaffyStyle;
+use taffy::tree::NodeId as TaffyNodeId;
+use taffy::Taffy as TaffyTree;
+
+use super::{BuildTree, BuildTreeExt, GenStyle};
+
+pub struct TaffyTreeBuilder<R: Rng, G: GenStyle<TaffyStyle>> {
+    rng: R,
+    style_generator: G,
+    tree: TaffyTree,
+    root: TaffyNodeId,
+}
+
+// Implement the BuildTree trait
+impl<R: Rng, G: GenStyle<TaffyStyle>> BuildTree<R, G> for TaffyTreeBuilder<R, G> {
+    type Tree = TaffyTree;
+    type Node = TaffyNodeId;
+
+    fn with_rng(mut rng: R, mut style_generator: G) -> Self {
+        let mut tree = TaffyTree::new();
+        let root = tree.new_leaf(style_generator.create_root_style(&mut rng)).unwrap();
+        TaffyTreeBuilder { rng, style_generator, tree, root }
+    }
+
+    fn random_usize(&mut self, range: impl SampleRange<usize>) -> usize {
+        self.rng.gen_range(range)
+    }
+
+    fn create_leaf_node(&mut self) -> Self::Node {
+        let style = self.style_generator.create_leaf_style(&mut self.rng);
+        self.tree.new_leaf(style).unwrap()
+    }
+
+    fn create_container_node(&mut self, children: &[Self::Node]) -> Self::Node {
+        let style = self.style_generator.create_container_style(&mut self.rng);
+        self.tree.new_with_children(style, children).unwrap()
+    }
+
+    fn total_node_count(&mut self) -> usize {
+        self.tree.total_node_count()
+    }
+
+    fn set_root_children(&mut self, children: &[Self::Node]) {
+        self.tree.set_children(self.root, children).unwrap();
+    }
+
+    fn into_tree_and_root(self) -> (Self::Tree, Self::Node) {
+        (self.tree, self.root)
+    }
+}
+
+impl<G: GenStyle<TaffyStyle>> BuildTreeExt<G> for TaffyTreeBuilder<ChaCha8Rng, G> {}
diff --git a/benches/src/yoga_helpers.rs b/benches/src/yoga_helpers.rs
index 3a5a96ba2..b05d7e61d 100644
--- a/benches/src/yoga_helpers.rs
+++ b/benches/src/yoga_helpers.rs
@@ -1,9 +1,15 @@
 #![allow(dead_code)]
+
+use rand::distributions::uniform::SampleRange;
+use rand::Rng;
+use rand_chacha::ChaCha8Rng;
 use slotmap::{DefaultKey, SlotMap};
 
+use super::{BuildTree, BuildTreeExt, GenStyle};
+
 pub mod yg {
     pub use ordered_float::OrderedFloat;
-    use slotmap::{DefaultKey, SlotMap};
+    pub use slotmap::{DefaultKey, SlotMap};
     pub use yoga::types::*;
     pub use yoga::Node;
 
@@ -13,31 +19,105 @@ pub mod yg {
 mod tf {
     pub use taffy::prelude::*;
 }
+use tf::Style as TaffyStyle;
+
+pub struct YogaTreeBuilder<R: Rng, G: GenStyle<TaffyStyle>> {
+    rng: R,
+    style_generator: G,
+    tree: yg::YogaTree,
+    root: yg::DefaultKey,
+}
+
+// Implement the BuildTree trait
+impl<R: Rng, G: GenStyle<TaffyStyle>> BuildTree<R, G> for YogaTreeBuilder<R, G> {
+    type Tree = yg::YogaTree;
+    type Node = DefaultKey;
+
+    fn with_rng(mut rng: R, mut style_generator: G) -> Self {
+        let mut tree = SlotMap::new();
+        let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]);
+        YogaTreeBuilder { rng, style_generator, tree, root }
+    }
+
+    fn random_usize(&mut self, range: impl SampleRange<usize>) -> usize {
+        self.rng.gen_range(range)
+    }
 
-pub fn new_default_style_with_children(
-    tree: &mut SlotMap<DefaultKey, yg::Node>,
-    children: Vec<DefaultKey>,
-) -> DefaultKey {
+    fn create_leaf_node(&mut self) -> Self::Node {
+        let style = self.style_generator.create_leaf_style(&mut self.rng);
+        create_yg_node(&mut self.tree, &style, &[])
+    }
+
+    fn create_container_node(&mut self, children: &[Self::Node]) -> Self::Node {
+        let style = self.style_generator.create_container_style(&mut self.rng);
+        create_yg_node(&mut self.tree, &style, &children)
+    }
+
+    fn set_root_children(&mut self, children: &[Self::Node]) {
+        set_node_children(&mut self.tree, self.root, &children);
+    }
+
+    fn total_node_count(&mut self) -> usize {
+        self.tree.len()
+    }
+
+    fn into_tree_and_root(self) -> (Self::Tree, Self::Node) {
+        (self.tree, self.root)
+    }
+}
+
+impl<G: GenStyle<TaffyStyle>> BuildTreeExt<G> for YogaTreeBuilder<ChaCha8Rng, G> {}
+
+// impl<R: Rng, G: GenStyle<TaffyStyle>> YogaTreeBuilder<R, G> {
+//     /// Create a YogaTreeBuilder with a standard rng from a style generator
+//     fn new<NG: GenStyle<TaffyStyle>>(mut style_generator: NG) -> YogaTreeBuilder<ChaCha8Rng, NG> {
+//         let mut rng = ChaCha8Rng::seed_from_u64(STANDARD_RNG_SEED);
+//         let mut tree = SlotMap::new();
+//         let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]);
+//         YogaTreeBuilder { rng, style_generator, tree, root }
+//     }
+
+//     /// Create a YogaTreeBuilder with a standard rng from a style generator
+//     fn with_seed<NG: GenStyle<TaffyStyle>>(seed: u64, mut style_generator: NG) -> YogaTreeBuilder<ChaCha8Rng, NG> {
+//         let mut rng = ChaCha8Rng::seed_from_u64(seed);
+//         let mut tree = SlotMap::new();
+//         let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]);
+//         YogaTreeBuilder { rng, style_generator, tree, root }
+//     }
+
+//     /// Create a YogaTreeBuilder from a random number generator and a style generator
+//     fn with_rng<NR: Rng, NG: GenStyle<TaffyStyle>>(mut rng: NR, mut style_generator: NG) -> YogaTreeBuilder<NR, NG> {
+//         let mut tree = SlotMap::new();
+//         let root = create_yg_node(&mut tree, &style_generator.create_root_style(&mut rng), &[]);
+//         YogaTreeBuilder { rng, style_generator, tree, root }
+//     }
+// }
+
+fn create_yg_node(tree: &mut yg::YogaTree, style: &tf::Style, children: &[yg::DefaultKey]) -> yg::DefaultKey {
     let mut node = yg::Node::new();
+    apply_taffy_style(&mut node, &style);
     for (i, child) in children.into_iter().enumerate() {
-        node.insert_child(&mut tree[child], i as u32);
+        node.insert_child(&mut tree[*child], i as u32);
     }
     tree.insert(node)
 }
 
-pub fn new_with_children(
-    tree: &mut SlotMap<DefaultKey, yg::Node>,
-    style: &tf::Style,
-    children: Vec<DefaultKey>,
-) -> DefaultKey {
+pub fn new_default_style_with_children(tree: &mut yg::YogaTree, children: &[yg::DefaultKey]) -> yg::DefaultKey {
     let mut node = yg::Node::new();
-    apply_taffy_style(&mut node, style);
     for (i, child) in children.into_iter().enumerate() {
-        node.insert_child(&mut tree[child], i as u32);
+        node.insert_child(&mut tree[*child], i as u32);
     }
     tree.insert(node)
 }
 
+fn set_node_children(tree: &mut yg::YogaTree, node_id: yg::DefaultKey, children: &[yg::DefaultKey]) {
+    // TODO: clear existing children.
+    for (i, child_id) in children.into_iter().enumerate() {
+        let [node, child] = tree.get_disjoint_mut([node_id, *child_id]).unwrap();
+        node.insert_child(child, i as u32);
+    }
+}
+
 fn into_yg_units(dim: impl Into<tf::Dimension>) -> yg::StyleUnit {
     match dim.into() {
         tf::Dimension::Auto => yg::StyleUnit::Auto,
@@ -93,7 +173,7 @@ fn content_into_justify(align: Option<tf::JustifyContent>) -> yg::Justify {
     }
 }
 
-pub fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) {
+fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) {
     // display
     node.set_display(match style.display {
         tf::Display::None => yg::Display::None,
diff --git a/src/tree/taffy_tree/tree.rs b/src/tree/taffy_tree/tree.rs
index effd39000..6ead32954 100644
--- a/src/tree/taffy_tree/tree.rs
+++ b/src/tree/taffy_tree/tree.rs
@@ -400,6 +400,11 @@ impl<NodeContext> Taffy<NodeContext> {
         Ok(self.children[parent_key][child_index])
     }
 
+    /// Returns the total number of nodes in the tree
+    pub fn total_node_count(&self) -> usize {
+        self.nodes.len()
+    }
+
     /// Returns the number of children of the `parent` node
     pub fn child_count(&self, parent: NodeId) -> TaffyResult<usize> {
         Ok(self.children[parent.into()].len())