tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81414 Sutichaykuna

Add more robust benchmarks (#165)

* Implement single root flat hierachy benchmark

* Fix typos

* Add trait for random instancing

* Fix errors in big tree benchmarks

* Register big tree benchmarks and set sample size to 10

* Fix cargo deny issue with taffy dev-dependency

* Add benchmarks for deeper tree hierarchy

* Adjust benchmark names because we don't have multiple roots anymore

* Adjust sample size of benchmarks

82471b8271f2915d2f905dd7fa3e1a1d56b22ab9 · ruwaq TimJentzsch · (4 wata ñawpaq) · tayta 2e0d1bd2f · kay commitpi sach'ata qhaway

Semántico sayk'uy · 12

benches/big_tree.rs

benches/complex.rs

src/lib.rs

src/randomizable.rs

5 qillqakuna · +161 −3

diff --git a/Cargo.toml b/Cargo.toml
index 292e30616..881173209 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ license = "MIT"
 [dependencies]
 arrayvec = { version = "0.7", default-features = false }
 num-traits = { version = "0.2", default-features = false }
+rand = { version = "0.8.5", optional = true }
 serde = { version = "1.0", optional = true, features = ["serde_derive"] }
 slotmap = "1.0.6"
 
@@ -24,10 +25,16 @@ default = ["std"]
 alloc = []
 std = ["num-traits/std"]
 serde = ["dep:serde"]
+random = ["dep:rand"]
 
 [dev-dependencies]
 criterion = "0.3"
 rstest = "0.15.0"
+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.2.0", features = ["random"] }
 
 [profile.release]
 lto = true
@@ -43,5 +50,10 @@ name = "complex"
 path = "benches/complex.rs"
 harness = false
 
+[[bench]]
+name = "big_tree"
+path = "benches/big_tree.rs"
+harness = false
+
 [workspace]
 members = ["scripts/gentest"]
diff --git a/benches/big_tree.rs b/benches/big_tree.rs
new file mode 100644
index 000000000..d9a45877d
--- /dev/null
+++ b/benches/big_tree.rs
@@ -0,0 +1,84 @@
+//! 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::FlexboxLayout;
+
+/// The number of nodes to include in the trees
+const NODE_COUNT: u32 = 100_000;
+
+/// Build a random leaf node
+fn build_random_leaf(taffy: &mut Taffy, rng: &mut ChaCha8Rng) -> Node {
+    taffy.new_with_children(FlexboxLayout::random(rng), &[]).unwrap()
+}
+
+/// A tree with many children that have shallow depth
+fn build_flat_hierarchy(taffy: &mut Taffy) -> Node {
+    let mut rng = ChaCha8Rng::seed_from_u64(12345);
+    let mut children = Vec::new();
+    let mut node_count = 0;
+
+    while node_count < 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_with_children(FlexboxLayout::random(&mut rng), &sub_children).unwrap();
+
+        children.push(node);
+        node_count += 1 + sub_children_count;
+    }
+
+    taffy.new_with_children(FlexboxLayout { ..Default::default() }, children.as_slice()).unwrap()
+}
+
+/// A helper function to recursively construct a deep tree
+fn build_deep_tree(taffy: &mut Taffy, rng: &mut ChaCha8Rng, max_nodes: u32) -> Vec<Node> {
+    let branching_factor = 7;
+
+    if max_nodes <= branching_factor {
+        // Build leaf nodes
+        return (0..max_nodes).map(|_| build_random_leaf(taffy, rng)).collect();
+    }
+
+    // Add another layer to the tree
+    // Each child gets an equal amount of the remaining nodes
+    (0..branching_factor)
+        .map(|_| {
+            let sub_children = build_deep_tree(taffy, rng, (max_nodes - branching_factor) / branching_factor);
+            taffy.new_with_children(FlexboxLayout::random(rng), &sub_children).unwrap()
+        })
+        .collect()
+}
+
+/// A tree with a higher depth for a more realistic scenario
+fn build_deep_hierarchy(taffy: &mut Taffy) -> Node {
+    let mut rng = ChaCha8Rng::seed_from_u64(12345);
+
+    let tree = build_deep_tree(taffy, &mut rng, NODE_COUNT);
+
+    taffy.new_with_children(FlexboxLayout { ..Default::default() }, &tree).unwrap()
+}
+
+fn taffy_benchmarks(c: &mut Criterion) {
+    // Decrease sample size, because the tasks take longer
+    let mut group = c.benchmark_group("big tree (100_000 nodes)");
+    group.sample_size(10);
+
+    group.bench_function("flat hierarchy", |b| {
+        let mut taffy = Taffy::new();
+        let root = build_flat_hierarchy(&mut taffy);
+
+        b.iter(|| taffy.compute_layout(root, Size::NONE).unwrap())
+    });
+
+    group.bench_function("deep hierarchy", |b| {
+        let mut taffy = Taffy::new();
+        let root = build_deep_hierarchy(&mut taffy);
+
+        b.iter(|| taffy.compute_layout(root, Size::NONE).unwrap())
+    });
+}
+
+criterion_group!(benches, taffy_benchmarks);
+criterion_main!(benches);
diff --git a/benches/complex.rs b/benches/complex.rs
index 7b1eec9fb..9c6545f65 100644
--- a/benches/complex.rs
+++ b/benches/complex.rs
@@ -96,14 +96,18 @@ fn build_deep_hierarchy(taffy: &mut taffy::node::Taffy) -> taffy::node::Node {
 }
 
 fn taffy_benchmarks(c: &mut Criterion) {
-    c.bench_function("deep hierarchy - build", |b| {
+    // Increase sample size, because the examples are very small
+    let mut group = c.benchmark_group("deep hierarchy");
+    group.sample_size(200);
+
+    group.bench_function("build", |b| {
         b.iter(|| {
             let mut taffy = taffy::node::Taffy::new();
             build_deep_hierarchy(&mut taffy);
         })
     });
 
-    c.bench_function("deep hierarchy - single", |b| {
+    group.bench_function("single", |b| {
         b.iter(|| {
             let mut taffy = taffy::node::Taffy::new();
             let root = build_deep_hierarchy(&mut taffy);
@@ -111,7 +115,7 @@ fn taffy_benchmarks(c: &mut Criterion) {
         })
     });
 
-    c.bench_function("deep hierarchy - relayout", |b| {
+    group.bench_function("relayout", |b| {
         let mut taffy = taffy::node::Taffy::new();
         let root = build_deep_hierarchy(&mut taffy);
 
diff --git a/src/lib.rs b/src/lib.rs
index 3e43875fa..d515f3475 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,6 +26,9 @@ pub mod node;
 pub mod prelude;
 pub mod style;
 
+#[cfg(feature = "random")]
+pub mod randomizable;
+
 mod data;
 mod flexbox;
 mod resolve;
diff --git a/src/randomizable.rs b/src/randomizable.rs
new file mode 100644
index 000000000..0a6b82389
--- /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::FlexboxLayout;
+
+/// 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 FlexboxLayout {
+    fn random<R>(rng: &mut R) -> Self
+    where
+        R: Rng,
+    {
+        // TODO: Add more attributes
+        FlexboxLayout { size: Size::<Dimension>::random(rng), ..Default::default() }
+    }
+}