Record PR 19 performance sweep and request invalidation fix
This commit is contained in:
1 parent
4c7072e62f
commit
048738bb9d
3 files changed
+137
-5
No files matched your search
@@ -6,6 +6,98 @@ nothing here is rediscovered. Each entry says who found it and when.
|
||||
it (settled design, the measurement method) belongs in `docs/LAYOUT.md`, and
|
||||
the current plan is in `docs/HANDOFF.md`.
|
||||
|
||||
## Performance sweep of #19 (2026-09-20)
|
||||
|
||||
`0e838e9`, by Codex.
|
||||
|
||||
The 5.89% text-resize increase at `8780b40` was real, but was not extra
|
||||
shaping: `revision_cost` at both `de1eb7e` and `8780b40` drew 168 widgets,
|
||||
rendered seven texts and broke seven layouts per resize frame. Sampling the
|
||||
latter put most CPU work in relocating retained primitives. This is a CPU
|
||||
layout/recording fixture, not a GPU text-rendering benchmark.
|
||||
|
||||
**Discarded discovery invalidated things it did not decide.** Both
|
||||
`Painter::size_request` and `measured_request` kept dependencies and pinned
|
||||
the rel base even when they discarded the discovered answer and used the
|
||||
ordinary measured size. Repainting one unchanged paragraph consequently
|
||||
redrew its enclosing span and remapped the other 39 paragraphs. Discovery now
|
||||
keeps those dependencies only when its answer is used; the measured path
|
||||
already records the size it reads. Used hints still register request readers:
|
||||
adding a cap to a previously unbounded hinted share must reach its allocator.
|
||||
The same edit is checked for an unhinted share too. `Widgets::size_rules` now
|
||||
borrows its rules instead of cloning both axes at every lookup; callers that
|
||||
need an owned snapshot clone explicitly.
|
||||
|
||||
All instruction measurements below are medians of nine executable runs with
|
||||
`perf stat -e instructions:u`, release builds without diagnostics. Every set
|
||||
kept all nine readings within 2% of its median. Totals include initialization
|
||||
and the cold frame. Resize uses 40 rows and 1,000 frames; the other phases use
|
||||
40 paragraphs in a scroll and 2,000 frames. Edit alternates a short suffix,
|
||||
rather than growing the workload every frame. `SWEEP=1` cycles 256 widths;
|
||||
the default toggles two widths. Billion retired instructions:
|
||||
|
||||
| Workload | Before deferred requests (`de1eb7e`) | Incoming (`8780b40`) | After this fix | After vs before deferred |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| Text resize, two widths | 9.340 | 9.890 | 9.432 | +0.99% |
|
||||
| Text resize, width sweep | 7.044 | 7.478 | 7.131 | +1.23% |
|
||||
| Unchanged paragraph repaint | 0.376 | 3.010 | 0.370 | -1.58% |
|
||||
| Paragraph edit | 3.485 | 3.607 | 3.492 | +0.22% |
|
||||
| Scroll | 0.428 | 0.430 | 0.428 | +0.10% |
|
||||
| Idle | 0.306 | 0.306 | 0.306 | +0.04% |
|
||||
|
||||
The repaint regression was eightfold in these process totals; diagnostics
|
||||
now show one text draw and no span draw. The remaining roughly 1% resize
|
||||
cost is discovery around content that still needs measurement. It is not
|
||||
necessary to weaken wrapping, overflow or bounds correctness to recover the
|
||||
regression. Differences below about half a percent should not be ranked:
|
||||
rebuilding changes instruction counts too.
|
||||
|
||||
The full PR is substantially cheaper than its upstream base `ca2b4b2` in
|
||||
these fixtures: that base takes 124.072B instructions for the two-width resize,
|
||||
13.516B for repaint, 14.155B for edit and 13.517B for scroll. Its geometry also
|
||||
differs (paragraphs run past the 900-pixel output), so this is a whole-version
|
||||
comparison, not equal-work evidence for a single optimization. The fixture
|
||||
was identical apart from spelling the fixed width `Len::abs(40.0)` there and
|
||||
omitting unavailable diagnostic calls.
|
||||
|
||||
**The generated trees are not all faster.** At depth 8, 1,000 frames, seeds
|
||||
1 and 13, the incoming branch's bounded resize workloads take 0.561B and
|
||||
4.297B instructions; the fix takes 0.539B and 4.016B. The pre-deferred branch
|
||||
uses 0.359B and 1.898B, but does different work: seed 13 draws 67 widgets per
|
||||
resize there and 170 with deferred requests, and even the active cold tree
|
||||
changes. Those are not equal-work comparisons. Removing intrinsic bounds
|
||||
with `IRIS_UNBOUNDED=1` isolates the ordinary path: seed 1 resize is 0.359B
|
||||
before vs 0.363B after, and seed 13 is 1.897B vs 1.296B. Unbounded seed 13's
|
||||
multi-widget repaint still rises from 0.980B to 1.208B. A consumed request's
|
||||
conservative invalidation remains a cost; eliminating it would require
|
||||
checking whether the request changed before invalidating its allocator.
|
||||
This sweep does not establish a universal speedup or make that cost inevitable.
|
||||
|
||||
**Storage and limits.** The forced-redraw plain and clamped allocation rigs
|
||||
still allocate zero times after warm-up. A single 2,000-row stress run
|
||||
(10,001 widgets, all retained) settles at 133,400 KiB RSS before deferred
|
||||
requests, 141,912 KiB incoming and 138,820 KiB after this fix. These process
|
||||
RSS snapshots show the retained bookkeeping cost, not a per-widget allocation
|
||||
size or a leak. The spare glyph store still copies text when keeping a prior
|
||||
width; its 128-entry bound does not bound bytes. The allocator visits expression
|
||||
nodes at each crossing; many distinct caps can require quadratic work. Neither
|
||||
case should be mistaken for the small fixed-width text-update workload above.
|
||||
|
||||
**GPU work is a separate tradeoff.** The first attempt detected RADV on the
|
||||
RX 7900 XT but could not allocate its 64 MiB device buffer (`No space left on
|
||||
device`). After allocation became available, both rigs ran successfully.
|
||||
Across three timestamp runs of `chain_cost` (200,000 two-pixel rectangles),
|
||||
depth 1 took 48–78 µs, depth 4 took 76–79 µs, depth 16 took 145–150 µs, and
|
||||
depth 64 took 430–443 µs. The first run's depth-2 result was anomalous; the two
|
||||
repeats agreed on approximately 78 µs for depths 1–4. Chain depth counts opted-in
|
||||
movable regions, not every widget. Deep chains have a real GPU cost, but 200,000
|
||||
small quads through 64 movable ancestors is a stress case, not a measurement
|
||||
of a normal paragraph or a phone. The CPU recording-only `draw_cost` rig also
|
||||
passed; its wall times under concurrent load are not used as regression evidence.
|
||||
No renderer or shader behavior changed in this performance fix.
|
||||
|
||||
Verification is recorded in `HANDOFF.md`. The app's Iris pin is unchanged.
|
||||
|
||||
## One ask, the root's included (2026-09-20)
|
||||
|
||||
`0d03267`. `root_layout` read the root's declared lengths against the window
|
||||
|
||||
Reference in new issue
Block a user