Record the fourteenth sweep, over allocation and the cost per child

The sweep is Iris `97fca76`. Its two largest findings are both costs per
thing rather than per frame: a container's draw searched a list once per
child and so cost the square of its children, and a mask's rectangle was
resolved once per fragment rather than once per instance. Both now have a
rig that can see them -- `children_cost.rs` is the only one here that varies
width, and `chain_cost.rs` has a masked fixture beside its two-pixel quads.

The sweep also settles the `Fixed::div`-versus-`ratio` item this file listed,
by deleting the operation nothing performed, and adds to the pre-gate
review's waiting list the `Arc<AtomicU32>` every `StrongWidget` allocates for
a count it can never raise.
This commit is contained in:
iris-ai committed 2026-09-20 23:58:34 -04:00
1 parent ea66fc3f71
commit d24ff83d6b
2 files changed
+214 -2

No files matched your search

+59 -2
View File
@@ -4,6 +4,55 @@ Where the work in flight stands. The settled layout design, the vocabulary
and the measurement method are in `docs/LAYOUT.md`; what the review of #19
found is in `docs/LAYOUT_LOG.md`.
## A sweep for allocation, and the cost per child, is on #19
The fourteenth sweep landed at `97fca76`. It read the whole branch again,
aimed first at what allocates and then at whatever the reading turned up.
Eight findings, the largest two both about cost per thing rather than per
frame:
- **A container's draw was quadratic in its children.** Five per-child steps
each asked "have I done this one already?" by searching a list; at 1,600
children 70% of the redraw was those searches. A draw now takes a `DrawId`
and leaves it on every widget it asks about, which answers the question in
one read -- one note per widget suffices because the handle a container holds
a child by cannot be cloned. `tests/children_cost.rs` is new and is the only
rig here that varies width: 3.680 ms to 0.811 ms at 1,600 children, and flat
per child at every count. A transcript is exactly this shape.
- **A mask's rectangle was resolved once per fragment**, a walk up to 64 links
long, although it is the same rectangle for every fragment of an instance.
Resolved in the vertex stage now, which takes `masks` and `move_offsets` out
of the fragment stage entirely. GPU timestamps over one screenful of clipped
rows: 838.4 us to 95.7 us at chain depth 64, 141.2 to 69.4 at depth 8, and
unchanged at depth 1. `chain_cost.rs` could not see this -- its instances are
two pixels wide so vertex work dominates -- and has a masked fixture now.
The other six: `TextBuffer::shape` copying its attrs before the check that
would not need them, which was one allocation per named-family text per frame
and 800 to 0 over a measured hundred frames; `cargo test --release` failing,
because a `#[should_panic]` test asserts a `debug_assert` release does not
compile; `Fixed::div` and `Div for Fixed`, reached only by their own test,
whose deletion also settles the `div`-versus-`ratio` zero-divisor item this
file used to list; `Moves::remove` re-uploading an array it cannot have
changed; and two comments this branch's own commits falsified -- one saying
`widget_trait!` takes no attributes after `76aaf06` taught it to, one saying
`Scroll` clips when masking is a capability a caller opts into with `Masked`.
It also left five things, with the numbers. Text allocates about six times per
re-broken paragraph per frame, and removing all of them is **0.07%** of
instructions (3.7107B to 3.7081B over 500 sweeping resize frames of 40
paragraphs), so the count is recorded rather than the machinery built --
allocation count and cost are not the same quantity, and this is where they
part. `StrongWidget` carries an `Arc<AtomicU32>` it can never use, since it has
no `Clone`; that and seven dead items older than #19 are listed under the
pre-gate review below rather than fixed here.
`docs/LAYOUT_LOG.md` has all of it. Verified: format, clippy both ways, 190
ordinary, 193 diagnostic and 189 release tests, the cold dump byte-identical to
`cbccfb6` across all 34,986 boxes, all three seed scans, the three deferred
corpora, and `mask_clip.rs` reading the clipped pixels back off the GPU to the
same 6,000 in the same bounds before and after the shader change.
## Built-in bounds replace `MaxSize` on PR #19
Bryan clarified that the built-in attribute should replace the wrapper.
@@ -238,6 +287,9 @@ comparison work above, its review rounds past `cadfba0` are described in
- **A full sweep of the branch**, `cbccfb6` -- a length of zero that printed
as nothing, a ceiling that stepped off the top of the grid, and a harness
setter that dropped the bound beside the length it set.
- **A sweep for allocation and the cost per child**, `97fca76` -- a
container's draw quadratic in its children, and a mask's rectangle resolved
once per fragment. Described at the top of this file.
The settled design of the vocabulary rounds is in `docs/LAYOUT.md` under
"Three names, and the one argument that says them". Bryan settled the API
@@ -349,6 +401,13 @@ a compile error, which reads exactly like a fuzzer failure.
grammars of which `core/src/util/vec2.rs` uses two, one line apart.
`Align` now has `Index<Axis>`, so the `if let Some` each
`partial_align` writes twice collapses when that sweep reaches them.
The fourteenth sweep adds more: `StrongWidget` allocates an
`Arc<AtomicU32>` refcount it can never use, because it deliberately has no
`Clone`, so every widget pays a heap allocation and two atomic
read-modify-writes for a count that stays at zero, and `StrongWidget::refs`
has no caller; and `Size::to_uivec2`, `Size::rel`, `Size::leftover`,
`Len::to_uivec2`, `Vec2::with_x`, `Vec2::with_y`, `Vec2::ceil` and
`Layers::iter_orderless_mut` are all dead.
3. **Integrate the app's Iris capabilities before changing its pin.**
`32f6ad8` has 45 commits not reachable from the review branch; shared UI
ownership, richer masks, Android support, and app-side performance work
@@ -380,8 +439,6 @@ glyphs do not follow a shortened entry.
`rel(1.0)` and shares `Painter::longer_than` with the span. Done in
`b295c8b`, and in `0d03267` for the root as well, which used to read it the
old way.
- `Fixed::div` by zero answers `MIN`/`MAX` while `ratio` answers `ZERO`; both
are caller bugs under `debug_assert`, but the fallbacks differ.
- `docs/LAYOUT.md` §4, §5 and the density section name `Painter::place`,
`Painter::region()`, `SetSize`, `desired_width`, `apply_rest`, `Len::dp`,
`Aligned`, which no longer exist; `MaxSize` now exists with the bounds API.