Record the extent-child and ordered-walk landings, and where the gap really is
The placement pin was not most of it. Deleting the read prices no dependency at all; expressing it properly is `e6ba570`, worth 19% of seed 13's `many` and 20% of `resize`. What refuses most reuses is the extent contract -- 819 of 1,203 refusals a frame -- and that is the measuring ask and the placing ask being two different extents, which `Span` is the remaining reader of. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
384b6a1150
commit
12b7364998
1 file changed
+79
-24
+79
-24
@@ -22,9 +22,10 @@ Its current head is `0e107f0` in `/home/bob/repos/iris-layout-experiment`.
|
|||||||
See **Frame and extent: retained prototype** for the mechanism, measured costs,
|
See **Frame and extent: retained prototype** for the mechanism, measured costs,
|
||||||
and the remaining cold/many-update work. The app's framework pin is unchanged.
|
and the remaining cold/many-update work. The app's framework pin is unchanged.
|
||||||
**It is not ready to replace #18 yet**: measured against `e44dea3` it wins on
|
**It is not ready to replace #18 yet**: measured against `e44dea3` it wins on
|
||||||
`size` and `scroll` and loses badly on `many` -- 6x on seed 13 at depth 8 --
|
`size` and `scroll` and loses badly on `many` -- 4.5x on seed 13 at depth 8
|
||||||
and the cause is one conservative dependency rather than the semantics. See
|
after the 2026-09-17 work -- and what is left of that is the measure/place
|
||||||
**Measured against #18's head, and the placement pin behind the gap**.
|
double draw rather than a conservative read. See **Measured against #18's
|
||||||
|
head, and the placement pin behind the gap** and the two sections after it.
|
||||||
|
|
||||||
**Widen one fuzzer axis at a time, and record which.** Seeds **1121** and
|
**Widen one fuzzer axis at a time, and record which.** Seeds **1121** and
|
||||||
**1839** at **depth 4** failed on `ea6dbae` and on every commit before it,
|
**1839** at **depth 4** failed on `ea6dbae` and on every commit before it,
|
||||||
@@ -707,31 +708,85 @@ the dependency:
|
|||||||
| seed 13, depth 8 | 1.088 / 524 | 6.351 / 2380 | 0.147 / 84 |
|
| seed 13, depth 8 | 1.088 / 524 | 6.351 / 2380 | 0.147 / 84 |
|
||||||
|
|
||||||
`cold` at seed 1 falls from 484 draws to 280 and 12.5 ms to 11.1 ms with it
|
`cold` at seed 1 falls from 484 draws to 280 and 12.5 ms to 11.1 ms with it
|
||||||
gone, and `resize` from 48 us to 12 us. So the pin is not a cost the frame and
|
gone, and `resize` from 48 us to 12 us. Read it as an upper bound on what
|
||||||
extent semantics require: it is `Pad` and `Stack` having no way to say "inside
|
`Pad` and `Stack` having no way to say "inside my extent" was costing, not as
|
||||||
my extent" other than reading the raw box.
|
what removing the read properly would buy -- see below for the difference.
|
||||||
|
|
||||||
**That is what `c44bd19` was, and the 1.8% that got it reverted was measured
|
**That is what `c44bd19` was, and the 1.8% that got it reverted was measured
|
||||||
before the dependency split.** Extending `widget_within` to accept
|
before the dependency split.** It is now landed as `e6ba570`, below, and the
|
||||||
`DrawRegion::Extent` was priced against a head where one accumulated range
|
bound above turned out to overstate it: deleting the read also deletes
|
||||||
still forced the redraws, so the saving had nowhere to show. Re-price it
|
invalidation that the extent semantics genuinely require, so it prices "no
|
||||||
against `0e107f0` before the experiment replaces #18; the enlarged child
|
dependency at all" rather than "the dependency expressed properly".
|
||||||
record it costs is 16 bytes against a phase that is presently 6x #18's on
|
|
||||||
seed 13.
|
|
||||||
|
|
||||||
**A smaller one, unrelated and free.** `redraw_updates` picks the deepest
|
### Landed on 2026-09-17: extent children, and an ordered walk
|
||||||
dirty widget by scanning the whole `needs_redraw` set and calling `depth` on
|
|
||||||
every member, once per pop, and `depth` is a hash lookup. Depth reads per
|
|
||||||
`many` frame at seed 1, depth 8: 131 at `IRIS_DIRTY=8`, 1,314 at 33, and
|
|
||||||
14,611 at 145, for 21, 62 and 182 pops. It is about 3% of a 33-dirty frame
|
|
||||||
(`depth` plus the hashbrown fold in `perf report`) and grows as the square of
|
|
||||||
the update set. Ordering the pops rather than re-scanning needs no retained
|
|
||||||
state.
|
|
||||||
|
|
||||||
Verified green at `0e107f0` on 2026-09-17 before any of the above:
|
Three commits on `wip/region-and-placement`, pushed, on top of `0e107f0`.
|
||||||
`cargo fmt --all --check`, `cargo clippy --workspace --all-targets -D
|
|
||||||
warnings`, and `cargo test --workspace` (106 suite, 20 core, 11 generated,
|
**`e6ba570`, a child gets a part of the container's extent.**
|
||||||
4 harness) all clean.
|
`widget_within` takes a `DrawRegion`, and `DrawRegion::Extent(part)` gives
|
||||||
|
the child a part of the extent without reading it. What is retained is the
|
||||||
|
part rather than the box it resolved to, so moving the extent re-places the
|
||||||
|
child through the same rule: `inherited_children` became `extent_children`,
|
||||||
|
carrying `Inherit` for the wrapper case `Painter::widget` already had and
|
||||||
|
`Within(part)` for the new one. `Pad` and `Stack` use it and no longer read
|
||||||
|
`placement()`. The dependency that goes up is a range on the container's
|
||||||
|
*extent*, since only the part's length reaches the child. A declared length
|
||||||
|
is unchanged -- it is a length of the frame wherever its box came from.
|
||||||
|
What still pins the placement is a report with a fraction in it, and that
|
||||||
|
pin is on the answer rather than the drawing; the test from the first
|
||||||
|
attempt fails without it.
|
||||||
|
|
||||||
|
**`3bf2293` and `34cafb6`, the walk takes the deepest mark from a
|
||||||
|
`BTreeSet` keyed by depth** rather than `max_by_key` over the whole set.
|
||||||
|
Every mark made while the walk runs goes through `mark`, which queues
|
||||||
|
itself; the set is still what says the walk is done, so a mark that arrived
|
||||||
|
another way cannot be left for the next frame. Depth reads per `many` frame
|
||||||
|
at seed 1 depth 8: 131/1,314/14,611 at 9/34/145 marks become 57/160/436.
|
||||||
|
What is drawn does not change at any load measured. Ties between equal
|
||||||
|
depths now break by widget id, which makes the walk deterministic.
|
||||||
|
|
||||||
|
Cycles, medians of seven alternating runs, `perf stat -e cycles:u`, against
|
||||||
|
both the branch #18 would merge and the experiment as it stood:
|
||||||
|
|
||||||
|
| phase | `e44dea3` | `0e107f0` | head | vs `0e107f0` |
|
||||||
|
| --- | ---: | ---: | ---: | ---: |
|
||||||
|
| `many`, seed 1 | 1.273 B | 2.543 B | 2.502 B | -1.6% |
|
||||||
|
| `many`, seed 13 | 1.323 B | 7.308 B | 5.931 B | -18.8% |
|
||||||
|
| `resize` | 0.269 B | 0.485 B | 0.389 B | -19.9% |
|
||||||
|
| `size` | 0.261 B | 0.205 B | 0.204 B | -0.2% |
|
||||||
|
| `scroll` | 1.730 B | 0.912 B | 0.939 B | +2.9% |
|
||||||
|
|
||||||
|
Verified at each commit: fmt, clippy with `-D warnings`, 109 suite and 20
|
||||||
|
core tests, the oracle at 100 seeds, the shrinker at 400 trees of depth 5,
|
||||||
|
1000 seeds at depth 6 and 2000 at depth 4 over all fifteen cases, and the
|
||||||
|
five reference renders plus `tabs` resized to 900x1200 and `random` to
|
||||||
|
1280x800, all byte-identical on Venus.
|
||||||
|
|
||||||
|
### What is left of the gap, and it is not the placement pin
|
||||||
|
|
||||||
|
**The extent contract is what refuses most reuses.** The diagnostics rig
|
||||||
|
now splits a failed reuse three ways. At seed 13, depth 8, `many`, of 1,203
|
||||||
|
refusals a frame: **819 fail an extent range**, 379 a frame range and 310
|
||||||
|
the placement pin (they overlap). Seed 1: 55, 20 and 37 of 98.
|
||||||
|
|
||||||
|
The point ranges are the tell -- `Holds { lo: 20, hi: 20 }`, `{ lo: 1916,
|
||||||
|
hi: 1916 }`. `Painter::px_len` narrows the extent to exactly the length
|
||||||
|
read, and the measuring ask and the placing ask are two different extents:
|
||||||
|
a widget that reads its pixel width while being measured cannot have that
|
||||||
|
drawing reused where it is finally placed. Wrapping text and `Scroll` both
|
||||||
|
do. That is the measure/place double draw, and it is why the experiment
|
||||||
|
draws 508 distinct widgets of 583 active where `e44dea3` draws 159.
|
||||||
|
|
||||||
|
**`Span` is the one raw placement read left that costs anything.** Replacing
|
||||||
|
`painter.placement()` with a non-recording read -- unsound, a bound only --
|
||||||
|
takes seed 13's `many` from 4.73 ms to 3.03 ms; doing the same to `Scroll`
|
||||||
|
moves nothing. A span cannot use an extent ask as it stands: it hands each
|
||||||
|
child the *frame* along the axis (so `rel(0.5)` is half the row) and a slot
|
||||||
|
cut from its own *extent*, and the slot is accumulated from lengths that are
|
||||||
|
fractions of the frame. Expressing those slots in extent coordinates needs
|
||||||
|
the frame-to-extent ratio, which is the inverse division this branch
|
||||||
|
removed. **Ask Bryan before designing that**; it is the shape of the
|
||||||
|
protocol, not a cleanup.
|
||||||
|
|
||||||
## How layout is decided
|
## How layout is decided
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user