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 · 2022-10-11T16:12:34Z (4 wata ñawpaq) · tayta 2e0d1bd2f · kay commitpi sach'ata qhaway
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() }
+ }
+}