Record frame-extent performance gains and alignment verification

This commit is contained in:
iris-ai committed 2026-09-17 15:54:23 -04:00
1 parent 8f1a5e0a46
commit 264dc4f0c2
2 files changed
+90 -42

No files matched your search

+86 -41
View File
@@ -422,8 +422,9 @@ back.
The original proposal was implemented by Claude as `5fcace1` on
`wip/region-and-placement` in `/home/bob/repos/iris-pr18`. It kept the fraction
reference stable but failed five layout cases and three draw-count cases.
The first correction is `efb416b`; the exact-recomposition continuation is
`2ed5503`, pushed to origin's `wip/region-and-placement`, in the isolated checkout
The first correction is `efb416b`, exact recomposition is `2ed5503`, and the
current performance/alignment continuation is **`7601aa2`**, pushed to origin's
`wip/region-and-placement`, in the isolated checkout
`/home/bob/repos/iris-layout-experiment`; **it is not on PR #18**. The original
checkout is unchanged. Keep the experiment's build directory separate: Cargo
can accept a different worktree's artifacts as fresh when a target directory
@@ -436,7 +437,7 @@ report must not replace the reference against which that report was obtained.
Text wraps at the available extent, not the whole fraction reference.
**The offer includes availability.** A span can keep the same reference frame
while offering a text less room after an earlier sibling. `known_len` therefore
while offering a text less room after an earlier sibling. `measure_len` therefore
accepts the placement as well as the frame. The first measurement's
`offer_placement` survives later placing evaluations, and local redraw asks
that original question before restoring the assigned slot. Frame-length
@@ -484,52 +485,96 @@ at arbitrarily wider widths. New tests compare actual primitive and mask
geometry and assert reuse for fixed-frame resizing and widening unwrapped
text (including explicit newlines and empty content).
**Measure the offer without placing an intermediate answer.** `Painter::measure_len`
replaces the hint/cache query and its duplicated drawing fallback. It still runs
`Widget::draw` when needed, but leaves that drawing in the offer until the parent
assigns the child's slot. Placing the child's own answer first was wasted work:
the parent immediately replaced that placement. The regression counts three
rather than four evaluations for a numeric-size leaf in a span, checks its actual
primitive bounds, and repeats after resize. There is no second layout body on
`Widget`. `Holds::ANY.through(...)` now returns `ANY` directly: an unrestricted
range needs no inverse division, including for a negative fraction.
**A chosen slot does not cancel a child's alignment.** Bryan caught this in the
`tabs` image: a fixed 100 px square under a flexible wrapper sat at the slot's
near edge. `ask_box` now aligns a declared frame inside the chosen slot, just as
it does inside an unassigned offer. A regression asserts the actual coordinates
before and after resize. Warm/cold equality could not find this because both
were wrong. The corrected 900 px image still has 88 px between blue and red and
78 px between red and orange: the enclosing `.pad(10)` adds 10 px before the
red slot. Red is centered in that slot, 5 px right of the visible black gap's
center. Do not describe those two centers as the same thing or compensate with
an arbitrary offset. The parked padding API change is still separate.
Ordinary verification passes: formatting, workspace clippy with
`layout-diagnostics`, workspace tests, and the fast generated cases. The
suite contains 98 tests. A `tabs` resize from 1920x1200
to 900x1200 matches a cold 900x1200 screenshot with zero differing pixels.
With exact equality, the final release oracle passes 2000 seeds at depth 4
and 1000 seeds at depth 6, the shrinker passes 400 seeds at depth 5, and the
debug oracle passes 120 seeds at depth 4 with assertions enabled. Every scan
runs all fifteen scenarios. The fixed-frame reuse regression was also run
against `efb416b`: it fails there with two child draws instead of one.
`layout-diagnostics`, workspace tests (100 suite tests and 21 core tests), and
the fast generated cases. The corrected `tabs` resize from 1920x1200 to
900x1200 matches a cold image with zero differing pixels. The release oracle
passes 2000 seeds at depth 4 and 1000 at depth 6; the shrinker passes 400 at
depth 5, and the debug oracle passes 120 at depth 4 with assertions enabled.
Every scan runs all fifteen scenarios with exact equality. The measurement
regression fails with four draws instead of three when the optimization is
disabled.
**Do not land this as a performance improvement.** The old stretching and
wrapper reuse tests pass, and one numeric-size test improves from three draws
to two. The continuation reduces widget draws from `efb416b` by 6.9% cold,
9.0% with many dirty widgets, 7.6% for a size change, and 8.7% for resize.
Against the last working `e44dea3`, it still does substantially more nested
layout work (seed 1, depth 8):
**Still not ready to replace PR #18.** The prototype does substantially more
nested layout work than `e44dea3`, even after removing intermediate placements
(seed 1, depth 8):
| phase | widget draws before | prototype | primitive writes before | prototype |
| phase | widget draws at e44dea3 | current | primitive writes at e44dea3 | current |
| --- | ---: | ---: | ---: | ---: |
| cold | 369 | 498 | 9635 | 9704 |
| cold | 369 | 484 | 9635 | 9179 |
| repaint | 1 | 1 | 1 | 1 |
| many | 157 | 282 | 4721 | 6398 |
| size | 16 | 254 | 106 | 5725 |
| many | 157 | 274 | 4721 | 5873 |
| size | 16 | 247 | 106 | 5200 |
| scroll | 2 | 2 | 0 | 0 |
| resize | 13 | 300 | 0 | 6255 |
| resize | 13 | 292 | 0 | 5730 |
These are instrumented work counts, not elapsed-time speedups (50 frames per
phase except cold, which is one; 578 generated widgets, 191 finally active).
Uninstrumented release binaries, run directly with `perf stat -r 3 -e
instructions:u`, also improve over `efb416b`: 2000 size-change frames fall
from 14.3923B to 13.8914B instructions (3.5%); 2000 resize frames fall from
16.9756B to 16.1331B (5.0%). These whole-process totals include fixture setup;
reported run-to-run variation is at most 0.24%. The many-dirty workload falls
from 16.7962B to 16.0030B instructions (4.7%, 2000 frames). Repaint and scroll
keep their draw counts but incur small CPU increases: 0.8849B to 0.8913B
(+0.7%) and 2.4332B to 2.4479B (+0.6%) over 100,000 frames each. These are
CPU-work measurements, not phone frame times. Keeping measurement validity separate from final drawing validity
and deferring to an already dirty parent were also tried; neither improved
this rig's draw counts, so those changes were removed.
These are instrumented work counts, not speedups (50 frames per phase except
cold, which is one; 578 generated widgets, 191 finally active). Against
`2ed5503`, the changes remove 7 widget draws and 525 primitive writes per size
frame, and 8 draws and 525 writes per resize frame.
Raw placement reads in containers and repeated offer/placement evaluations
remain conservative. Retaining local coordinates also costs memory per
primitive. The experiment demonstrates selective reuse with one `Widget::draw`
body, not a finished performance replacement. Optimize the container protocol
under the same correctness tests before adopting it. The parked `Inset`/`Outset`
API changes are not included in this experiment.
**Measure cycles as well as instructions** (Bryan, 2026-09-17). The following
are whole-process medians from seven alternating before/after runs of direct,
uninstrumented release test executables under `perf stat -e
cycles:u,instructions:u`. The baseline is `39f7b08`: `2ed5503` with the same
centering fix, so both sides draw the corrected layout. CPU-heavy scans were stopped
during measurement. Counts include fixture setup; size, resize and many run
5000 frames, repaint and scroll run 1,000,000. These are VM CPU measurements,
not phone frame times.
| phase | cycles before → after (billions) | instructions before → after (billions) |
| --- | ---: | ---: |
| size | 12.8008 → 11.0344 (-13.8%) | 34.3660 → 29.9450 (-12.9%) |
| resize | 14.9368 → 13.0376 (-12.7%) | 39.8487 → 35.0032 (-12.2%) |
| many | 14.8875 → 12.9579 (-13.0%) | 39.6021 → 34.7499 (-12.3%) |
| repaint | 2.7416 → 2.6626 (-2.9%) | 6.0266 → 6.0197 (-0.1%) |
| scroll | 9.7848 → 8.4829 (-13.3%) | 21.7203 → 20.9527 (-3.5%) |
The size/resize/many cycle samples have nonoverlapping before/after ranges,
with each range less than 1.3% of its median. Additional depth-6 fixtures,
10,000 frames and seven alternating pairs each: seed 3 improves cycles by
7.0% for size and 8.4% for resize; seed 13 improves resize by 9.2%, while its
size samples overlap and establish no cycle improvement.
**The remaining gap has not been established as an unavoidable correctness
cost.** A temporary failure trace of one frame on seed 1, depth 8 counted
39/108 failed reuse attempts in size, 65/151 in resize, and 45/109 in many
where only the raw placement snapshot differed: both numeric frame and extent
ranges still held. Containers read that snapshot to construct child positions,
so discarding the check would be wrong; a way to retain those child positions
relative to the extent is the next protocol question. There is also necessary
width-dependent work: the hot text at widget 199 visits 68 px and 89.53613 px
and reports different heights (316.80078 and 228.80078). It is evaluated
repeatedly at those widths, so that fact alone does not justify all the repeats.
Keeping measurement validity separate from final drawing validity and deferring
to an already dirty parent were previously tried without improving draw counts.
Measuring first in local redraw and then restoring its slot saved only one draw
in `many` and added another request on simple repaints; that trial was removed.
Retaining local coordinates still costs memory per primitive. Optimize under
the exact oracle before adopting the prototype; the `Inset`/`Outset` changes
remain parked.
## How layout is decided