tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definiciones

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 · por Nico Burns · (hace 3 años) · padre 4e1c35c5d · ver el árbol en este commit

Delta semántico · 51

benches/helpers/mod.rs

benches/helpers/yoga_helpers.rs

benches/yoga_helpers.rs

src/compute/mod.rs

src/debug.rs

9 archivos · +626 −431

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;