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 · por Nico Burns · 2023-01-24T15:57:44Z (hace 3 años) · padre 0596475af · ver el árbol en este commit
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,