CSS Grid Benchmarks (#340)
* Rename big_tree benchmarks to big_tree_flexbox
* Add CSS Grid benchmarks
* Disable largest grid tests
* Fix unused parameter warning
* Add smaller large sized grid benchmark
* Lowercase grid benchmark names
* Another larger size wide benchmark
* Add 2x2 benchmarks
* Fmt helpers
* Add superdeep benchmarks
* Actually benchmark Grid algorithm in wide benchmarks!
* Add profile feature and time!() macro
* Reduce sample size for wide benchmarks
* Make wide grid benchmarks use order of magnitude node counts
* Allow profiling macro to be unused
1624a68d7a2e09de1c73f70dffa098e358793753 · ruwaq Nico Burns · (3 wata ñawpaq) · tayta 4e1c35c5d · kay commitpi sach'ata qhaway
Semántico sayk'uy · 51
benches/big_tree_flexbox.rs
benches/helpers/yoga_helpers.rs
diff --git a/Cargo.toml b/Cargo.toml
index 4fd46f380..756141fc6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,6 +30,7 @@ serde = ["dep:serde"]
random = ["dep:rand"]
yoga_benchmark = []
debug = []
+profile = []
[dev-dependencies]
criterion = "0.4"
@@ -57,8 +58,13 @@ path = "benches/complex.rs"
harness = false
[[bench]]
-name = "big_tree"
-path = "benches/big_tree.rs"
+name = "big_tree_flexbox"
+path = "benches/big_tree_flexbox.rs"
+harness = false
+
+[[bench]]
+name = "big_tree_grid"
+path = "benches/big_tree_grid.rs"
harness = false
[workspace]
diff --git a/benches/big_tree.rs b/benches/big_tree_flexbox.rs
similarity index 92%
rename from benches/big_tree.rs
rename to benches/big_tree_flexbox.rs
index 588e95d69..75edb4553 100644
--- a/benches/big_tree.rs
+++ b/benches/big_tree_flexbox.rs
@@ -6,8 +6,11 @@ use taffy::prelude::*;
use taffy::randomizable::Randomizeable;
use taffy::style::Style;
+mod helpers;
+use helpers::build_deep_tree;
+
#[cfg(feature = "yoga_benchmark")]
-mod yoga_helpers;
+use helpers::yoga_helpers;
#[cfg(feature = "yoga_benchmark")]
use slotmap::SlotMap;
#[cfg(feature = "yoga_benchmark")]
@@ -62,30 +65,6 @@ fn build_yoga_flat_hierarchy(total_node_count: u32) -> (yg::YogaTree, Node) {
(tree, root)
}
-/// A helper function to recursively construct a deep tree
-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_flex_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_flex_node);
- create_flex_node(tree, sub_children)
- })
- .collect()
-}
-
/// A tree with a higher depth for a more realistic scenario
fn build_taffy_deep_hierarchy(node_count: u32, branching_factor: u32) -> (Taffy, Node) {
let mut rng = ChaCha8Rng::seed_from_u64(12345);
diff --git a/benches/big_tree_grid.rs b/benches/big_tree_grid.rs
new file mode 100644
index 000000000..23c4f3e0e
--- /dev/null
+++ b/benches/big_tree_grid.rs
@@ -0,0 +1,161 @@
+//! 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 std::iter;
+use taffy::prelude::*;
+use taffy::style::Style;
+
+/// Build a random leaf node
+fn build_random_leaf(taffy: &mut Taffy, _rng: &mut ChaCha8Rng) -> Node {
+ taffy.new_with_children(Style { size: points(20.0), ..Default::default() }, &[]).unwrap()
+}
+
+fn random_grid_track<R: Rng>(rng: &mut R) -> TrackSizingFunction {
+ let switch: f32 = rng.gen_range(0.0..=1.0);
+ if switch < 0.1 {
+ auto()
+ } else if switch < 0.2 {
+ min_content()
+ } else if switch < 0.3 {
+ max_content()
+ } else if switch < 0.5 {
+ flex(1.0)
+ } else if switch < 0.6 {
+ minmax(points(0.0), flex(1.0))
+ } else if switch < 0.8 {
+ points(40.0)
+ } else {
+ percent(0.3)
+ }
+}
+
+fn random_nxn_grid_style<R: Rng>(rng: &mut R, track_count: usize) -> Style {
+ Style {
+ display: Display::Grid,
+ grid_template_columns: iter::from_fn(|| Some(random_grid_track(rng))).take(track_count).collect(),
+ grid_template_rows: iter::from_fn(|| Some(random_grid_track(rng))).take(track_count).collect(),
+ ..Default::default()
+ }
+}
+
+/// A tree with many children that have shallow depth
+fn build_grid_flat_hierarchy(col_count: usize, row_count: usize) -> (Taffy, Node) {
+ let mut taffy = Taffy::new();
+ let mut rng = ChaCha8Rng::seed_from_u64(12345);
+
+ let style = Style {
+ display: Display::Grid,
+ grid_template_columns: iter::from_fn(|| Some(random_grid_track(&mut rng))).take(col_count).collect(),
+ grid_template_rows: iter::from_fn(|| Some(random_grid_track(&mut rng))).take(row_count).collect(),
+ ..Default::default()
+ };
+
+ let children: Vec<_> =
+ iter::from_fn(|| Some(build_random_leaf(&mut taffy, &mut rng))).take(col_count * row_count).collect();
+
+ let root = taffy.new_with_children(style, children.as_slice()).unwrap();
+ (taffy, root)
+}
+
+/// A helper function to recursively construct a deep tree
+pub fn build_deep_grid_tree(
+ tree: &mut Taffy,
+ levels: usize,
+ track_count: usize,
+ create_leaf_node: &mut impl FnMut(&mut Taffy) -> Node,
+ create_container_node: &mut impl FnMut(&mut Taffy, Vec<Node>) -> Node,
+) -> Vec<Node> {
+ // The extra one is for a position:absolute child
+ let child_count = track_count.pow(2) + 1;
+
+ if levels == 1 {
+ // Build leaf nodes
+ return (0..child_count).map(|_| create_leaf_node(tree)).collect();
+ }
+
+ // Add another layer to the tree
+ // Each child gets an equal amount of the remaining nodes
+ (0..child_count)
+ .map(|_| {
+ let sub_children =
+ build_deep_grid_tree(tree, levels - 1, track_count, create_leaf_node, create_container_node);
+ tree.set_style(sub_children[child_count - 1], {
+ let mut style = tree.style(sub_children[child_count - 1]).unwrap().clone();
+ style.position = Position::Absolute;
+ style.size = percent(0.5);
+ style
+ })
+ .unwrap();
+ create_container_node(tree, sub_children)
+ })
+ .collect()
+}
+
+/// A tree with a higher depth for a more realistic scenario
+fn build_taffy_deep_grid_hierarchy(levels: usize, track_count: usize) -> (Taffy, 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_with_children(random_nxn_grid_style(&mut rng, track_count), &children).unwrap()
+ };
+
+ let mut taffy = Taffy::new();
+ let tree = build_deep_grid_tree(&mut taffy, levels, track_count, &mut build_leaf_node, &mut build_flex_node);
+ let root = taffy.new_with_children(Style::DEFAULT, &tree).unwrap();
+ (taffy, root)
+}
+
+fn taffy_benchmarks(c: &mut Criterion) {
+ let mut group = c.benchmark_group("grid/wide");
+ group.sample_size(10);
+ for track_count in [31usize, 100, 316].iter() {
+ group.bench_with_input(
+ BenchmarkId::new(format!("{c}x{c}", c = track_count), track_count.pow(2)),
+ track_count,
+ |b, &track_count| {
+ b.iter_batched(
+ || build_grid_flat_hierarchy(track_count, track_count),
+ |(mut taffy, root)| taffy.compute_layout(root, points(12000.0)).unwrap(),
+ criterion::BatchSize::SmallInput,
+ )
+ },
+ );
+ }
+ group.finish();
+
+ let mut group = c.benchmark_group("grid/deep");
+ group.sample_size(10);
+ for (tracks, levels) in [(3, 3), (3, 4) /*, (3, 5)*/, (2, 3), (2, 4), (2, 5), (2, 6)].iter() {
+ let children_per_level: usize = (tracks * tracks) + 1;
+ group.bench_with_input(
+ BenchmarkId::new(format!("{c}x{c}", c = tracks), children_per_level.pow(*levels as u32)),
+ &(*levels, *tracks),
+ |b, &(levels, tracks)| {
+ b.iter_batched(
+ || build_taffy_deep_grid_hierarchy(levels, tracks),
+ |(mut taffy, root)| taffy.compute_layout(root, points(12000.0)).unwrap(),
+ criterion::BatchSize::SmallInput,
+ )
+ },
+ );
+ }
+ group.finish();
+
+ let mut group = c.benchmark_group("grid/superdeep");
+ group.sample_size(10);
+ for levels in [10, 20].iter() {
+ group.bench_with_input(BenchmarkId::new("1x1", levels), levels, |b, &levels| {
+ b.iter_batched(
+ || build_taffy_deep_grid_hierarchy(levels, 1),
+ |(mut taffy, root)| taffy.compute_layout(root, max_content()).unwrap(),
+ criterion::BatchSize::SmallInput,
+ )
+ });
+ }
+ group.finish();
+}
+
+criterion_group!(benches, taffy_benchmarks);
+criterion_main!(benches);
diff --git a/benches/helpers/mod.rs b/benches/helpers/mod.rs
new file mode 100644
index 000000000..87638b2d9
--- /dev/null
+++ b/benches/helpers/mod.rs
@@ -0,0 +1,27 @@
+#[cfg(feature = "yoga_benchmark")]
+mod yoga_helpers;
+
+/// A helper function to recursively construct a deep tree
+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()
+}
diff --git a/benches/yoga_helpers.rs b/benches/helpers/yoga_helpers.rs
similarity index 100%
rename from benches/yoga_helpers.rs
rename to benches/helpers/yoga_helpers.rs
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 8e9a20307..6008d8a3a 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -20,7 +20,7 @@ use self::flexbox::FlexboxAlgorithm;
use self::grid::CssGridAlgorithm;
use self::leaf::LeafAlgorithm;
-#[cfg(feature = "debug")]
+#[cfg(any(feature = "debug", feature = "profile"))]
use crate::debug::NODE_LOGGER;
/// Updates the stored layout of the provided `node` and its children
@@ -130,7 +130,7 @@ fn compute_node_layout(
run_mode: RunMode,
sizing_mode: SizingMode,
) -> SizeAndBaselines {
- #[cfg(feature = "debug")]
+ #[cfg(any(feature = "debug", feature = "profile"))]
NODE_LOGGER.push_node(node);
#[cfg(feature = "debug")]
println!();
@@ -144,7 +144,7 @@ fn compute_node_layout(
NODE_LOGGER.labelled_debug_log("CACHE", cached_size_and_baselines.size);
#[cfg(feature = "debug")]
debug_log_node(known_dimensions, parent_size, available_space, run_mode, sizing_mode);
- #[cfg(feature = "debug")]
+ #[cfg(any(feature = "debug", feature = "profile"))]
NODE_LOGGER.pop_node();
return cached_size_and_baselines;
}
@@ -234,7 +234,7 @@ fn compute_node_layout(
#[cfg(feature = "debug")]
NODE_LOGGER.labelled_debug_log("RESULT", computed_size_and_baselines.size);
- #[cfg(feature = "debug")]
+ #[cfg(any(feature = "debug", feature = "profile"))]
NODE_LOGGER.pop_node();
computed_size_and_baselines
diff --git a/src/debug.rs b/src/debug.rs
index 65ea31770..991cbc4a2 100644
--- a/src/debug.rs
+++ b/src/debug.rs
@@ -104,5 +104,27 @@ impl DebugLogger {
}
}
-#[cfg(feature = "debug")]
+#[cfg(any(feature = "debug", feature = "profile"))]
pub(crate) static NODE_LOGGER: DebugLogger = DebugLogger::new();
+
+#[cfg(feature = "profile")]
+#[allow(unused_macros)]
+macro_rules! time {
+ ($label:expr, $($code:tt)*) => {
+ let start = ::std::time::Instant::now();
+ $($code)*
+ let duration = ::std::time::Instant::now().duration_since(start);
+ crate::debug::NODE_LOGGER.log(format_args!("Performed {} in {}ms", $label, duration.as_millis()));
+ };
+}
+
+#[cfg(not(feature = "profile"))]
+#[allow(unused_macros)]
+macro_rules! time {
+ ($label:expr, $($code:tt)*) => {
+ $($code)*
+ };
+}
+
+#[allow(unused_imports)]
+pub(crate) use time;