perf(layout): bound initial numeric cache reservation

Reserve at most four existing boxes on the first store and grow with actual cache demand. Avoid the full-tree candidate scan and cover sparse stores in both flattened and ordinary block trees.
This commit is contained in:
ldm0
2026-10-18 20:01:57 +08:00
committed by Donough Liu
parent 515fe0c484
commit a46e654706
2 changed files with 38 additions and 39 deletions
@@ -101,37 +101,42 @@ fn numeric_cache_growth_keeps_box_viewport_and_invalidation_owners_distinct() {
}
#[test]
fn numeric_cache_allocation_follows_actual_stores_in_a_large_flattened_tree() {
let mut world = LayoutWorld::new(block(0), false);
for source in 1..=4096 {
let mut text = block(source);
text.inline_flattened = true;
text.inline_context_owner = Some(world.root());
world.boxes.push(text);
fn numeric_cache_allocation_follows_stores_in_large_flattened_and_block_trees() {
for flattened in [false, true] {
let mut world = LayoutWorld::new(block(0), false);
for source in 1..=4096 {
let mut child = block(source);
if flattened {
child.inline_flattened = true;
child.inline_context_owner = Some(world.root());
}
world.boxes.push(child);
}
let sibling = LayoutBoxId::from_index(world.len());
world.boxes.push(block(4097));
let input = input();
let root = world.root().to_taffy();
world.cache_store(root, &input, output(60.0));
world.cache_store(sibling.to_taffy(), &input, output(80.0));
assert_eq!(world.cache_get(root, &input), Some(output(60.0)));
assert_eq!(
world.cache_get(sibling.to_taffy(), &input),
Some(output(80.0))
);
let allocated_bytes = world.box_caches.capacity() * std::mem::size_of::<taffy::Cache>();
assert!(
allocated_bytes < 64 * 1024,
"4098 construction boxes with two stores should not allocate dense caches: {allocated_bytes} bytes"
);
assert_eq!(world.box_caches.len(), 2);
// Unvisited boxes, including flattened boxes, can still store later.
let previously_unvisited = LayoutBoxId::from_index(4096).to_taffy();
world.cache_store(previously_unvisited, &input, output(90.0));
assert_eq!(
world.cache_get(previously_unvisited, &input),
Some(output(90.0))
);
}
let sibling = LayoutBoxId::from_index(world.len());
world.boxes.push(block(4097));
let input = input();
let root = world.root().to_taffy();
world.cache_store(root, &input, output(60.0));
world.cache_store(sibling.to_taffy(), &input, output(80.0));
assert_eq!(world.cache_get(root, &input), Some(output(60.0)));
assert_eq!(
world.cache_get(sibling.to_taffy(), &input),
Some(output(80.0))
);
let allocated_bytes = world.box_caches.capacity() * std::mem::size_of::<taffy::Cache>();
assert!(
allocated_bytes < 64 * 1024,
"4098 construction boxes with two stores should not allocate dense caches: {allocated_bytes} bytes"
);
// Admission hints must not prevent a later valid store on a flattened box.
let previously_unvisited = LayoutBoxId::from_index(4096).to_taffy();
world.cache_store(previously_unvisited, &input, output(90.0));
assert_eq!(
world.cache_get(previously_unvisited, &input),
Some(output(90.0))
);
}
#[test]
+3 -9
View File
@@ -706,15 +706,9 @@ where
slot
} else {
if self.box_caches.is_empty() {
// Reserve without initializing caches for unvisited boxes.
// This is a hint, not an eligibility restriction: a caller
// may subsequently store any valid construction/numeric box.
let candidates = self
.boxes
.iter()
.filter(|layout_box| !layout_box.inline_flattened)
.count();
self.box_caches.reserve_exact(candidates);
// Bound the first allocation without scanning construction
// boxes or reserving caches for boxes that may never store.
self.box_caches.reserve_exact(self.boxes.len().min(4));
}
let slot = u32::try_from(self.box_caches.len())
.ok()