The frame report says what it measured: idle is not stutter, waiting is not late
Iris's phone came back "now THAT is smooth", and reading that run against the bench's own timings found three things the report was getting wrong -- two of them shipped yesterday in the fix for the last three. `missed vsyncs` counted idleness. Every gap between frames was treated as cadence, so the bench's own pauses read as stutter: 276 for sixteen 300ms rests between flings, 2410 for twelve hundred 50ms keystroke gaps, 821 for four hundred 50ms stream gaps -- each within a few percent of the arithmetic. A gap now measures anything only if the frame before it had asked for another one. `late` counted the swapchain wait as cost. A well-paced loop spends each frame blocked in the acquire, so its total sits at exactly one refresh period and every frame lands on the budget boundary -- 0.4ms of work and 5.7ms of waiting is not a late frame. It is judged on `FrameParts::work`. And the refresh rate is the larger of what the platform claims and what the run sustained, because each can only be wrong one way. `Display.getRefreshRate()` answered 60 for a run that drew 3405 frames in 33.1s, since a phone that varies its rate answers with whatever mode it is in when asked. The first attempt at measuring it instead took the fastest tenth of the gaps and reported 88Hz for this repo's 60Hz emulator, whose app manages 54 -- a budget no frame there could meet, invented out of the app's best moments, and caught only by running the corrected report on the emulator before shipping it. A sustained rate is a floor and cannot do that. Both are printed when they disagree. Also corrected in the docs: "103fps on a 120Hz screen" divided the fling phase by its whole duration, rests included. Both runs sustained ~120.3fps through the motion, so the callback ordering was never costing frames -- what changed is the clock, which moves no frame count at all, which is exactly why nothing in a report could show it. `fling_profile.rs` is `frame_profile.rs` and gained a stream run, which says where the frame time now is: folding an arriving event is 0.35ms and applying the diff 0.41ms, while the frame is 3.86ms here and 9.5ms on the phone. 401 events move the item count 652 -> 654, so nearly every one is a delta into the same row -- the cost is re-shaping one growing message, not `fold_event`'s per-event clone, which was the hypothesis and is what measuring it ruled out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
42d54eec95
commit
9bf714fa2e
6 files changed
+457
-57
No files matched your search
@@ -389,21 +389,28 @@ Each exists because something was invisible without it.
|
||||
since the rig lives in iris and the app's examples do not). The emulator is for JNI, the IME, insets, the surface
|
||||
lifecycle and one verification run before a build goes to the phone --
|
||||
not for iterating on layout.
|
||||
- **`app-rust/tests/fling_profile.rs`** is what a fling frame costs on
|
||||
the CPU, at layer 1 -- `cargo test --release --test fling_profile --
|
||||
--ignored --nocapture`, from `app-rust/`. It flings the real transcript
|
||||
screen over the bench fixture eight times, out and back, and prints the
|
||||
per-frame distribution with a count of how many frames did any layout at
|
||||
all. `#[ignore]`d and assertion-free, so `run-tests.sh` neither runs it
|
||||
nor can fail on it; **release or the numbers mean nothing**, since text
|
||||
shaping dominates. Two things it established on 2026-09-09 that are
|
||||
worth not re-deriving: only about one fling frame in six lays anything
|
||||
out (the rest are moved on the GPU through `move_offsets`), and the
|
||||
- **`app-rust/tests/frame_profile.rs`** is what a frame costs on the CPU,
|
||||
at layer 1 -- `cargo test --release --test frame_profile -- --ignored
|
||||
--nocapture`, from `app-rust/`. Two runs: a fling over the bench
|
||||
fixture eight times out and back, and a reply streaming into it one
|
||||
event at a time. `#[ignore]`d and assertion-free, so `run-tests.sh`
|
||||
neither runs it nor can fail on it; **release or the numbers mean
|
||||
nothing**, since text shaping dominates. It cannot answer anything
|
||||
about the GPU, the swapchain or the phone's own clock.
|
||||
|
||||
What it established on 2026-09-09, worth not re-deriving. A **fling**
|
||||
is not CPU-bound: only about one frame in six lays anything out (the
|
||||
rest are moved on the GPU through `move_offsets`), and the
|
||||
multi-millisecond spikes are all in the *first* pass over a stretch of
|
||||
transcript -- every later pass over the same rows is p99 0.26ms. So a
|
||||
warm fling is not CPU-bound in iris, and a phone report showing
|
||||
otherwise is measuring something else. It cannot answer anything about
|
||||
the GPU, the swapchain or the phone's own clock.
|
||||
transcript -- every later pass over the same rows is p99 0.26ms. A
|
||||
**streamed event** is, and it is not where it looks: folding the event
|
||||
is 0.35ms and applying the diff to the widget tree is 0.41ms, while the
|
||||
*frame* is 3.86ms here and 9.5ms on Iris's phone. 401 streamed events
|
||||
move the item count from 652 to 654, so almost every one is a delta
|
||||
into the same row -- the cost is re-laying out and re-shaping one
|
||||
growing message on every delta, not the fold. (The fold was the
|
||||
hypothesis, from `foldEvent`'s Compose lesson under "Things that have
|
||||
bitten"; measuring it is what ruled it out.)
|
||||
- **The emulator is a GLES rig, deliberately** (Iris, 2026-09-08;
|
||||
docs/RUST.md). Its guest has no hardware Vulkan -- only SwiftShader
|
||||
in software -- while its GLES *is* the host's real GPU through virgl at
|
||||
|
||||
Reference in new issue
Block a user