tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 499HTML 238Markdown 7TOML 31441 Definitions

Implement head-to-head benchmarks against Yoga (#337)

* Slim down benchmark suite

* Use loops for defining benchmarks

* Implement head-to-head benchmarks with yoga

* Implement all taffy big_tree benchmarks for yoga

* Hide yoga benchmarks behind a feature flag

* Update yoga benchmarks in the README

* Add missing benchmark table row

* Allow ISC and BSD-3-Clause licenses

4e1c35c5d40729fdaafc40afeea863aa12ed1d5a · by Nico Burns · (3 years ago) · parent 0596475af · browse the tree at this commit

Semantic delta · 22

benches/yoga_helpers.rs

src/style/dimension.rs

6 files · +372 −259

diff --git a/Cargo.toml b/Cargo.toml
index 2bc0121c8..4fd46f380 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ alloc = []
 std = ["num-traits/std"]
 serde = ["dep:serde"]
 random = ["dep:rand"]
+yoga_benchmark = []
 debug = []
 
 [dev-dependencies]
@@ -35,6 +36,8 @@ criterion = "0.4"
 rstest = "0.16.0"
 rand = "0.8.5"
 rand_chacha = "0.3.1"
+yoga = "0.4.0"
+ordered-float = "3.4.0"
 
 # Enable example and test-specific features
 taffy = { path = ".", features = ["random"] }
diff --git a/README.md b/README.md
index eb3868f17..4323f7ae3 100644
--- a/README.md
+++ b/README.md
@@ -73,21 +73,28 @@ assert_eq!(taffy.layout(body_node).unwrap().size.height, 500.0); // This value w
 
 ## Benchmarks (vs. [Yoga](https://github.com/facebook/yoga))
 
-- Run on a 2021 MacBook Pro with M1 Pro processor.
-- Taffy benchmarks are using criterion (10 iterations).
-- Yoga benchmarks run via it's node.js bindings (the `yoga-layout-prebuilt` npm package), they were run a few times manually and it was verified that variance in the numbers of each run was minimal. It should be noted that this is using an old version of Yoga.
-
-(note that the table below contains multiple different units (milliseconds vs. microseconds vs. nanoseconds))
-
-| Benchmark | Yoga | Taffy 0.2 |
-| --- | --- | --- |
-| yoga/10 nodes (1-level hierarchy) | 45.1670 µs | 1.9857 µs |
-| yoga/100 nodes (2-level hierarchy) | 134.1250 µs | 41.810 µs |
-| yoga/1_000 nodes (3-level hierarchy) | 1.2221 ms | 357.48 µs |
-| yoga/10_000 nodes (4-level hierarchy) | 13.8672 ms | 3.7310 ms |
-| yoga/100_000 nodes (5-level hierarchy) | 141.5307 ms | 39.682 ms |
-
-Most popular websites seem to have between 3,000 and 10,000 nodes (although they also require text layout, which neither yoga nor taffy implement).
+- Run on a 2021 MacBook Pro with M1 Pro processor using [criterion](https://github.com/bheisler/criterion.rs)
+- The benchmarks measure layout computation only. They do not measure tree creation.
+- Yoga benchmarks were run via the [yoga](https://github.com/bschwind/yoga-rs) crate (Rust bindings)
+- Most popular websites seem to have between 3,000 and 10,000 nodes (although they also require text layout, which neither yoga nor taffy implement).
+
+Note that the table below contains multiple different units (milliseconds vs. microseconds
+
+| Benchmark          | Node Count | Depth | Yoga ([ba27f9d]) | Taffy ([9059647]) |
+| ---                | ---        | ---   | ---              | ---               |
+| yoga 'huge nested' | 1,000      | 3     | 411.42 µs        | 275.47 µs         |
+| yoga 'huge nested' | 10,000     | 4     | 3.9882 ms        | 3.9409 ms         |
+| yoga 'huge nested' | 100,000    | 5     | 46.117 ms        | 32.458 ms         |
+| big trees (wide)   | 1,000      | 1     | 750.75 µs        | 571.35 µs         |
+| big trees (wide)   | 10,000     | 1     | 7.1639 ms        | 7.4838 ms         |
+| big trees (wide)   | 100,000    | 1     | 132.17 ms        | 245.16 ms         |
+| big trees (deep)   | 4,000      | 12    | 2.3140 ms        | 2.0300 ms         |
+| big trees (deep)   | 10,000     | 14    | 6.0009 ms        | 5.1872 ms         |
+| big trees (deep)   | 100,000    | 17    | 76.954 ms        | 74.946 ms         |
+| super deep         | 1,000      | 1,000 | 563.15 µs        | 548.97 µs         |
+
+[ba27f9d]: https://github.com/facebook/yoga/commit/ba27f9d1ecfa7518019845b84b035d3d4a2a6658
+[9059647]: https://github.com/DioxusLabs/taffy/commit/ba27f9d1ecfa7518019845b84b035d3d4a2a6658
 
 ## Contributions
 
diff --git a/benches/big_tree.rs b/benches/big_tree.rs
index 1f7d2a523..588e95d69 100644
--- a/benches/big_tree.rs
+++ b/benches/big_tree.rs
@@ -1,45 +1,78 @@
 //! This file includes benchmarks for very large, pseudo-randomly generated trees
-use criterion::{criterion_group, criterion_main, Criterion};
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
 use rand::prelude::*;
 use rand_chacha::ChaCha8Rng;
 use taffy::prelude::*;
 use taffy::randomizable::Randomizeable;
 use taffy::style::Style;
 
+#[cfg(feature = "yoga_benchmark")]
+mod yoga_helpers;
+#[cfg(feature = "yoga_benchmark")]
+use slotmap::SlotMap;
+#[cfg(feature = "yoga_benchmark")]
+use yoga_helpers::yg;
+
 /// Build a random leaf node
 fn build_random_leaf(taffy: &mut Taffy, rng: &mut ChaCha8Rng) -> Node {
     taffy.new_with_children(Style::random(rng), &[]).unwrap()
 }
 
 /// A tree with many children that have shallow depth
-fn build_flat_hierarchy(taffy: &mut Taffy, total_node_count: u32) -> Node {
+fn build_taffy_flat_hierarchy(total_node_count: u32) -> (Taffy, Node) {
+    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<Node> = (0..sub_children_count).map(|_| build_random_leaf(taffy, &mut rng)).collect();
+        let sub_children: Vec<Node> =
+            (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;
     }
 
-    taffy.new_with_children(Style { ..Default::default() }, children.as_slice()).unwrap()
+    let root = taffy.new_with_children(Style::DEFAULT, children.as_slice()).unwrap();
+    (taffy, root)
+}
+
+#[cfg(feature = "yoga_benchmark")]
+/// A tree with many children that have shallow depth
+fn build_yoga_flat_hierarchy(total_node_count: u32) -> (yg::YogaTree, Node) {
+    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<Node> = (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;
+    }
+
+    let root = yoga_helpers::new_with_children(&mut tree, &Style::DEFAULT, children);
+    (tree, root)
 }
 
 /// A helper function to recursively construct a deep tree
-fn build_deep_tree(
-    taffy: &mut Taffy,
+fn build_deep_tree<T, N>(
+    tree: &mut T,
     max_nodes: u32,
     branching_factor: u32,
-    create_leaf_node: &mut impl FnMut(&mut Taffy) -> Node,
-    create_flex_node: &mut impl FnMut(&mut Taffy, Vec<Node>) -> Node,
-) -> Vec<Node> {
+    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(taffy)).collect();
+        return (0..max_nodes).map(|_| create_leaf_node(tree)).collect();
     }
 
     // Add another layer to the tree
@@ -47,27 +80,46 @@ fn build_deep_tree(
     (0..branching_factor)
         .map(|_| {
             let max_nodes = (max_nodes - branching_factor) / branching_factor;
-            let sub_children = build_deep_tree(taffy, max_nodes, branching_factor, create_leaf_node, create_flex_node);
-            create_flex_node(taffy, sub_children)
+            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_deep_hierarchy(taffy: &mut Taffy, node_count: u32, branching_factor: u32) -> Node {
+fn build_taffy_deep_hierarchy(node_count: u32, branching_factor: u32) -> (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(Style::random(&mut rng), &children).unwrap();
 
-    let tree = build_deep_tree(taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
+    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)
+}
+
+#[cfg(feature = "yoga_benchmark")]
+/// A tree with a higher depth for a more realistic scenario
+fn build_yoga_deep_hierarchy(node_count: u32, branching_factor: u32) -> (yg::YogaTree, Node) {
+    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<Node>| {
+        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);
 
-    taffy.new_with_children(Style { ..Default::default() }, &tree).unwrap()
+    (tree, root)
 }
 
 /// A deep tree that matches the shape and styling that yoga use on their benchmarks
-fn build_yoga_deep_hierarchy(taffy: &mut Taffy, node_count: u32, branching_factor: u32) -> Node {
+fn build_taffy_huge_nested_hierarchy(node_count: u32, branching_factor: u32) -> (Taffy, Node) {
     let style = Style {
         size: Size { width: Dimension::Points(10.0), height: Dimension::Points(10.0) },
         flex_grow: 1.0,
@@ -77,246 +129,141 @@ fn build_yoga_deep_hierarchy(taffy: &mut Taffy, node_count: u32, branching_facto
     let mut build_flex_node =
         |taffy: &mut Taffy, children: Vec<Node>| taffy.new_with_children(style.clone(), &children).unwrap();
 
-    let tree = build_deep_tree(taffy, node_count, branching_factor, &mut build_leaf_node, &mut build_flex_node);
-
-    taffy.new_with_children(Style::DEFAULT, &tree).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 taffy_benchmarks(c: &mut Criterion) {
-    let mut group = c.benchmark_group("yoga benchmarks");
-    group.sample_size(10);
-
-    group.bench_function("10 nodes", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_yoga_deep_hierarchy(&mut taffy, 10, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("100 nodes", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_yoga_deep_hierarchy(&mut taffy, 100, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("1_000 nodes", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_yoga_deep_hierarchy(&mut taffy, 1_000, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("10_000 nodes", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_yoga_deep_hierarchy(&mut taffy, 10_000, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
+#[cfg(feature = "yoga_benchmark")]
+/// 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, Node) {
+    let style = Style {
+        size: Size { width: Dimension::Points(10.0), height: Dimension::Points(10.0) },
+        flex_grow: 1.0,
+        ..Default::default()
+    };
+    let mut build_leaf_node = |tree: &mut yg::YogaTree| -> Node {
+        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<Node>| -> Node {
+        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)
+    };
 
-    group.bench_function("100_000 nodes", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_yoga_deep_hierarchy(&mut taffy, 100_000, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
+    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)
+}
 
-    group.bench_function("1_000_000 nodes", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_yoga_deep_hierarchy(&mut taffy, 1_000_000, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
-    drop(group);
+fn taffy_benchmarks(c: &mut Criterion) {
+    let mut group = c.benchmark_group("yoga 'huge nested'");
+    for node_count in [1_000u32, 10_000, 100_000].iter() {
+        #[cfg(feature = "yoga_benchmark")]
+        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),
+                |(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| {
+            b.iter_batched(
+                || build_taffy_huge_nested_hierarchy(node_count, 10),
+                |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
+                criterion::BatchSize::SmallInput,
+            )
+        });
+    }
+    group.finish();
 
     // Decrease sample size, because the tasks take longer
     let mut group = c.benchmark_group("big trees (wide)");
     group.sample_size(10);
-
-    group.bench_function("10 nodes (2-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_flat_hierarchy(&mut taffy, 10);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("100 nodes (2-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_flat_hierarchy(&mut taffy, 100);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("1_000 nodes (2-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_flat_hierarchy(&mut taffy, 1_000);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("10_000 nodes (2-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_flat_hierarchy(&mut taffy, 10_000);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
-
-    group.bench_function("100_000 nodes (2-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_flat_hierarchy(&mut taffy, 100_000);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
-
-    group.bench_function("100_000 nodes (7-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 100_000, 7);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
-
-    drop(group);
+    for node_count in [1_000u32, 10_000, 100_000].iter() {
+        #[cfg(feature = "yoga_benchmark")]
+        let benchmark_id = BenchmarkId::new(format!("Yoga (2-level hierarchy)"), node_count);
+        #[cfg(feature = "yoga_benchmark")]
+        group.bench_with_input(benchmark_id, node_count, |b, &node_count| {
+            b.iter_batched(
+                || build_yoga_flat_hierarchy(node_count),
+                |(mut tree, root)| {
+                    tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR);
+                },
+                criterion::BatchSize::SmallInput,
+            )
+        });
+        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),
+                |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
+                criterion::BatchSize::SmallInput,
+            )
+        });
+    }
+    group.finish();
 
     // Decrease sample size, because the tasks take longer
     let mut group = c.benchmark_group("big trees (deep)");
     group.sample_size(10);
+    let benches = [(4000, "(12-level hierarchy)"), (10_000, "(14-level hierarchy)"), (100_000, "(17-level hierarchy)")];
+    for (node_count, label) in benches.iter() {
+        #[cfg(feature = "yoga_benchmark")]
+        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),
+                |(mut tree, root)| {
+                    tree[root].calculate_layout(f32::INFINITY, f32::INFINITY, yg::Direction::LTR);
+                },
+                criterion::BatchSize::SmallInput,
+            )
+        });
+        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),
+                |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
+                criterion::BatchSize::SmallInput,
+            )
+        });
+    }
+    group.finish();
 
-    group.bench_function("4000 nodes (12-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 4000, 2);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("10_000 nodes (14-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 10_000, 2);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("100_000 nodes (17-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 100_000, 2);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
-
-    group.bench_function("1_000_000 nodes (20-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 1_000_000, 2);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::LargeInput,
-        )
-    });
-
-    drop(group);
-
-    let mut group = c.benchmark_group("super deep trees");
+    let mut group = c.benchmark_group("super deep (1000-level hierarchy)");
     group.sample_size(10);
-
-    group.bench_function("100 nodes (100-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 100, 1);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
-
-    group.bench_function("1_000 nodes (1000-level hierarchy)", |b| {
-        b.iter_batched(
-            || {
-                let mut taffy = Taffy::new();
-                let root = build_deep_hierarchy(&mut taffy, 1_000, 1);
-                (taffy, root)
-            },
-            |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
-            criterion::BatchSize::SmallInput,
-        )
-    });
+    for node_count in [1000u32].iter() {
+        #[cfg(feature = "yoga_benchmark")]
+        group.bench_with_input(BenchmarkId::new("Yoga", node_count), node_count, |b, &node_count| {
+            b.iter_batched(
+                || build_yoga_deep_hierarchy(node_count, 2),
+                |(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| {
+            b.iter_batched(
+                || build_taffy_deep_hierarchy(node_count, 2),
+                |(mut taffy, root)| taffy.compute_layout(root, Size::MAX_CONTENT).unwrap(),
+                criterion::BatchSize::SmallInput,
+            )
+        });
+    }
+    group.finish();
 }
 
 criterion_group!(benches, taffy_benchmarks);
diff --git a/benches/yoga_helpers.rs b/benches/yoga_helpers.rs
new file mode 100644
index 000000000..165ebdfea
--- /dev/null
+++ b/benches/yoga_helpers.rs
@@ -0,0 +1,154 @@
+use slotmap::{DefaultKey, SlotMap};
+
+pub mod yg {
+    pub use ordered_float::OrderedFloat;
+    use slotmap::{DefaultKey, SlotMap};
+    pub use yoga::types::*;
+    pub use yoga::Node;
+
+    pub type YogaTree = SlotMap<DefaultKey, Node>;
+}
+mod tf {
+    pub use taffy::prelude::*;
+}
+
+pub fn new_with_children(
+    tree: &mut SlotMap<DefaultKey, yg::Node>,
+    style: &tf::Style,
+    children: Vec<DefaultKey>,
+) -> 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);
+    }
+    tree.insert(node)
+}
+
+fn into_yg_units(dim: impl Into<tf::Dimension>) -> yg::StyleUnit {
+    match dim.into() {
+        tf::Dimension::Auto => yg::StyleUnit::Auto,
+        tf::Dimension::Points(val) => yg::StyleUnit::Point(yg::OrderedFloat(val)),
+        tf::Dimension::Percent(val) => yg::StyleUnit::Percent(yg::OrderedFloat(val)),
+    }
+}
+
+fn into_pixels(dim: impl Into<tf::Dimension>) -> f32 {
+    dim.into().into_option().unwrap_or(0.0)
+}
+
+fn items_into_align(align: Option<tf::AlignSelf>) -> yg::Align {
+    match align {
+        None => yg::Align::Auto,
+        Some(tf::AlignSelf::FlexStart) => yg::Align::FlexStart,
+        Some(tf::AlignSelf::FlexEnd) => yg::Align::FlexEnd,
+        Some(tf::AlignSelf::Center) => yg::Align::Center,
+        Some(tf::AlignSelf::Baseline) => yg::Align::Baseline,
+        Some(tf::AlignSelf::Stretch) => yg::Align::Stretch,
+        Some(tf::AlignSelf::Start) => unimplemented!(),
+        Some(tf::AlignSelf::End) => unimplemented!(),
+    }
+}
+
+fn content_into_align(align: Option<tf::AlignContent>) -> yg::Align {
+    match align {
+        None => yg::Align::Auto,
+        Some(tf::AlignContent::FlexStart) => yg::Align::FlexStart,
+        Some(tf::AlignContent::Start) => yg::Align::FlexStart,
+        Some(tf::AlignContent::FlexEnd) => yg::Align::FlexEnd,
+        Some(tf::AlignContent::End) => yg::Align::FlexEnd,
+        Some(tf::AlignContent::Center) => yg::Align::Center,
+        Some(tf::AlignContent::Stretch) => yg::Align::Stretch,
+        Some(tf::AlignContent::SpaceBetween) => yg::Align::SpaceBetween,
+        Some(tf::AlignContent::SpaceAround) => yg::Align::SpaceAround,
+        Some(tf::AlignContent::SpaceEvenly) => unimplemented!(),
+    }
+}
+
+fn content_into_justify(align: Option<tf::JustifyContent>) -> yg::Justify {
+    match align {
+        None => yg::Justify::FlexStart,
+        Some(tf::JustifyContent::FlexStart) => yg::Justify::FlexStart,
+        Some(tf::JustifyContent::Start) => yg::Justify::FlexStart,
+        Some(tf::JustifyContent::FlexEnd) => yg::Justify::FlexEnd,
+        Some(tf::JustifyContent::End) => yg::Justify::FlexEnd,
+        Some(tf::JustifyContent::Center) => yg::Justify::Center,
+        Some(tf::JustifyContent::SpaceBetween) => yg::Justify::SpaceBetween,
+        Some(tf::JustifyContent::SpaceAround) => yg::Justify::SpaceAround,
+        Some(tf::JustifyContent::Stretch) => unimplemented!(),
+        Some(tf::JustifyContent::SpaceEvenly) => unimplemented!(),
+    }
+}
+
+pub fn apply_taffy_style(node: &mut yg::Node, style: &tf::Style) {
+    // display
+    node.set_display(match style.display {
+        tf::Display::Flex => yg::Display::Flex,
+        tf::Display::Grid => panic!("Yoga does not support CSS Grid"),
+        tf::Display::None => yg::Display::None,
+    });
+
+    // position
+    node.set_position_type(match style.position {
+        tf::Position::Relative => yg::PositionType::Relative,
+        tf::Position::Absolute => yg::PositionType::Absolute,
+    });
+    // inset
+    node.set_position(yg::Edge::Left, into_yg_units(style.inset.left));
+    node.set_position(yg::Edge::Right, into_yg_units(style.inset.right));
+    node.set_position(yg::Edge::Top, into_yg_units(style.inset.top));
+    node.set_position(yg::Edge::Bottom, into_yg_units(style.inset.bottom));
+
+    // sizes
+    node.set_width(into_yg_units(style.size.width));
+    node.set_height(into_yg_units(style.size.height));
+    node.set_min_width(into_yg_units(style.min_size.width));
+    node.set_min_height(into_yg_units(style.min_size.height));
+    node.set_max_width(into_yg_units(style.max_size.width));
+    node.set_max_height(into_yg_units(style.max_size.height));
+
+    // aspect_ratio
+    if let Some(aspect_ratio) = style.aspect_ratio {
+        node.set_aspect_ratio(aspect_ratio);
+    }
+
+    // spacing
+    node.set_padding(yg::Edge::Left, into_yg_units(style.padding.left));
+    node.set_padding(yg::Edge::Right, into_yg_units(style.padding.right));
+    node.set_padding(yg::Edge::Top, into_yg_units(style.padding.top));
+    node.set_padding(yg::Edge::Bottom, into_yg_units(style.padding.bottom));
+    node.set_margin(yg::Edge::Left, into_yg_units(style.margin.left));
+    node.set_margin(yg::Edge::Right, into_yg_units(style.margin.right));
+    node.set_margin(yg::Edge::Top, into_yg_units(style.margin.top));
+    node.set_margin(yg::Edge::Bottom, into_yg_units(style.margin.bottom));
+    node.set_border(yg::Edge::Left, into_pixels(style.border.left));
+    node.set_border(yg::Edge::Right, into_pixels(style.border.right));
+    node.set_border(yg::Edge::Top, into_pixels(style.border.top));
+    node.set_border(yg::Edge::Bottom, into_pixels(style.border.bottom));
+
+    // alignment
+    node.set_align_items(items_into_align(style.align_items));
+    node.set_align_self(items_into_align(style.align_self));
+    node.set_align_content(content_into_align(style.align_content));
+    node.set_justify_content(content_into_justify(style.justify_content));
+
+    // gap
+    node.set_column_gap(into_pixels(style.gap.width));
+    node.set_row_gap(into_pixels(style.gap.height));
+
+    // flex
+    node.set_flex_direction(match style.flex_direction {
+        tf::FlexDirection::Row => yg::FlexDirection::Row,
+        tf::FlexDirection::Column => yg::FlexDirection::Column,
+        tf::FlexDirection::RowReverse => yg::FlexDirection::RowReverse,
+        tf::FlexDirection::ColumnReverse => yg::FlexDirection::ColumnReverse,
+    });
+    node.set_flex_wrap(match style.flex_wrap {
+        tf::FlexWrap::NoWrap => yg::Wrap::NoWrap,
+        tf::FlexWrap::Wrap => yg::Wrap::Wrap,
+        tf::FlexWrap::WrapReverse => yg::Wrap::WrapReverse,
+    });
+    node.set_flex_basis(into_yg_units(style.flex_basis));
+    node.set_flex_grow(style.flex_grow);
+    node.set_flex_shrink(style.flex_shrink);
+}
diff --git a/deny.toml b/deny.toml
index 40cf233fd..1283ab103 100644
--- a/deny.toml
+++ b/deny.toml
@@ -17,6 +17,8 @@ allow = [
     "Unicode-DFS-2016",
     "Apache-2.0",
     "Zlib",
+    "ISC",
+    "BSD-3-Clause"
 ]
 default = "deny"
 
@@ -31,7 +33,7 @@ skip = [
     { name = "taffy" },
 
     # This dependency won't actually end up in the final binary anyway as it a sub-dependency
-    # of num-cpus and atty only when targetted the hermit unikernel OS
+    # of num-cpus and atty only when targetting the hermit unikernel OS
     # (https://github.com/hermitcore/rusty-hermit)
     { name = "hermit-abi" }
 ]
diff --git a/src/style/dimension.rs b/src/style/dimension.rs
index bd280cd4e..b2c98c618 100644
--- a/src/style/dimension.rs
+++ b/src/style/dimension.rs
@@ -142,7 +142,7 @@ impl Dimension {
 
     /// Get Points value if value is Points variant
     #[cfg(feature = "grid")]
-    pub(crate) fn into_option(self) -> Option<f32> {
+    pub fn into_option(self) -> Option<f32> {
         match self {
             Dimension::Points(value) => Some(value),
             _ => None,