Record what fixed point cost, and what it was

Three times slower in layout, and two thirds of that was a stacked child
redrawing twice a frame rather than the arithmetic. The counters are what
found it, once they could say "another layer" at all.

The performance table is rewritten around the fixed-point commits and in
milliseconds a frame, since the fixture it used has changed and the old
instruction counts cannot be compared across it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 04:07:27 -04:00
1 parent 2d860587a4
commit 6081726314
1 file changed
+48 -16
+48 -16
View File
@@ -8,7 +8,7 @@ log.
Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18 Canonical Iris `main` is **`ca2b4b2`** (#17, the headless rig). **#18
`split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local `split/18-position-chain`** is open in `/home/bob/repos/iris-pr18`; its local
head is **`bdab558`**, fifty-seven commits. Built-in alignment is complete there; head is **`f11f5f4`**, sixty commits. Built-in alignment is complete there;
see "Built-in alignment" below for the retained-layout details. No PR reviews see "Built-in alignment" below for the retained-layout details. No PR reviews
were present when checked on 2026-09-15. were present when checked on 2026-09-15.
@@ -72,12 +72,12 @@ rewrite:
`core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle `core/src/ui/holds.rs`, the retained tests, and the generated cold-layout oracle
pin those rules. Seeds 10 and 86 are now in the ordinary generated set. pin those rules. Seeds 10 and 86 are now in the ordinary generated set.
## Verification at `bdab558` ## Verification at `f11f5f4`
- `cargo fmt --all --check` - `cargo fmt --all --check`
- `cargo build --workspace --all-features` - `cargo build --workspace --all-features`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` - `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `cargo test --workspace --all-features`: 103 passed, 10 ignored - `cargo test --workspace --all-features`: 105 passed, 10 ignored
- The release generated cold-layout oracle passed 100 seeds in 9.4 s, and a - The release generated cold-layout oracle passed 100 seeds in 9.4 s, and a
shrinker case at 300 seeds in 3.5 s: both run a thread per core but one. shrinker case at 300 seeds in 3.5 s: both run a thread per core but one.
- The release shrinker passed **all five** cases -- `resize`, `repaint`, - The release shrinker passed **all five** cases -- `resize`, `repaint`,
@@ -120,24 +120,51 @@ choice and the incompatible visual effect of making `Full` the default.
### Performance ### Performance
The retained rewrite was compared with #18's previous head `691e3eb` using the Measured 2026-09-16 on the release `layout_diagnostics` fixture, seed 1,
same release `layout_diagnostics` fixture, seed 1, depth 8, 500 frames, 130 depth 8, 500 frames, as median milliseconds a frame. `5ed9e87` is the commit
dirty widgets: before fixed point; `39e4ca2` is fixed point complete.
| phase | `691e3eb` | `29c7881` | | phase | `5ed9e87` | `39e4ca2` | `f11f5f4` |
| --- | ---: | ---: | | --- | ---: | ---: | ---: |
| many | 25.17M instructions/frame | 6.14M | | many | 0.179 ms | 0.544 | 0.278 |
| resize | 16.08M | 8.39M | | resize | 0.020 | 0.035 | 0.041 |
| scroll | 0.720M | 0.691M | | scroll | 0.011 | 0.030 | 0.020 |
| repaint | 0.720M | 0.689M | | repaint | 0.012 | 0.031 | 0.028 |
| size | 0.730M | 0.698M |
The final `29c7881` measurements were 3,070,693,965 instructions for 500 **Fixed point cost 3x, and two thirds of that was not the grid.** The
`many` frames and 4,192,804,214 for 500 `resize` frames. Re-run before quoting counters said eight more "placed by redrawing" a frame in `scroll`, all of
tighter figures. them "reuse: another layer": a retained drawing belongs to the layer it was
made on, and `Stack` measured the child that sizes it on its own layer before
drawing it again on the child layer. `97cc8b3` measures on the layer the
child ends up on, which puts the scroll phase's counters back exactly where
they were -- 4 widget draws, 12 draw requests.
What is left is per-operation cost, not more work: at `f11f5f4` the `many`
phase does 125 widget draws against 121 before, and takes 1.6x as long. The
arithmetic is the difference -- an `i64` multiply and a rounding branch where
there was an `f32` multiply. The profile's shape, in order: `InstanceList::
push`, `Painter::glyphs`, `RegionRemap::apply_span`, `UiRegion::within`. The
one obvious thing left is `glyphs`, which converts four `f32`s per glyph:
a `GlyphEntry` holding `Px` would convert once when the glyph is rasterised
instead of on every frame that draws it.
Earlier, against #18's own history: the retained rewrite took `many` from
25.17M instructions a frame at `691e3eb` to 6.14M at `29c7881`, and `resize`
from 16.08M to 8.39M. That fixture has since changed; do not compare across
it.
## Retained-layout invariants ## Retained-layout invariants
- **A retained drawing belongs to the layer it was made on.** Asked for again
on another layer it is redrawn, since nothing about its geometry says it is
in a list that paints at a different moment. A container that measures a
child by drawing it therefore measures on the layer that child will draw on
-- `Painter::child_layer_at` addresses one -- or it pays two draws a frame
forever and keeps whichever the second ask left.
- A widget that clips its contents to its box reports its box: `Scroll` and
`Masked` both report `LEFTOVER`, and a `debug_assert` in `draw_at` holds
any widget that set a mask to it. Overflowing is otherwise ordinary and a
text too tall for its box says so.
- A span is as long across itself as its longest child, unless a rule beside - A span is as long across itself as its longest child, unless a rule beside
it says how long it is -- and then it does not read its children there at it says how long it is -- and then it does not read its children there at
all, since the answer is not wanted and reading one is what makes its size all, since the answer is not wanted and reading one is what makes its size
@@ -458,6 +485,11 @@ the same `Holds` contract and box chain.
Queued from this work, in order: Queued from this work, in order:
- `Painter::glyphs` converts four `f32`s to `Px` per glyph, every frame that
draws it. A `GlyphEntry` holding `Px` would convert once, when the glyph is
rasterised. It is the largest single thing left in the layout profile after
`InstanceList::push`.
- `Scroll` should take a direction rather than one axis: vertical, horizontal, - `Scroll` should take a direction rather than one axis: vertical, horizontal,
or both. Reporting `LEFTOVER` on both axes is already the right shape for it. or both. Reporting `LEFTOVER` on both axes is already the right shape for it.
- Restore `max_width`/`max_height` as `SizeRule::{Min, Max, Clamp}`. `8220a78` - Restore `max_width`/`max_height` as `SizeRule::{Min, Max, Clamp}`. `8220a78`