android: the boot chain goes out in one round, and a census that holds it there
Thirty rounds of onMount had never been read end to end, and read end to end it was a queue: two dozen commands with a critical path ten deep, because Tauri's `listen` is an invoke like any other. loadLibrary asked for five independent SQLite reads one behind the other, bindSlsk registered eight listeners one behind the other, and watchLibrary and the player's bindEvents did two each. None of them depended on anything before them. They go out in one round now — the same commands, critical path 10 to 3, and ten of them used to leave in the shell's own first turn where twenty-three do now — and in a rig that runs the real store modules with the bridge instrumented, lib.loaded lands at 53 ms instead of 85 at a 6 ms round trip and 62 instead of 148 at 20 ms. Those are a desktop rig's milliseconds with the round trip swept rather than measured; the phone's own numbers are unproven until the walk prints bootMs and bootLibMs. The behaviour is meant to be identical. loadLibrary's only movable part was lib.error, which has two writers and used to be decided by the order they were awaited in; the two messages are now carried out of the round and applied in that same order, so a failing library still shows the library's message. Every combination of which of the five reads fails, over six latency shapes, comes out identical to the old code. bindSlsk still does not return until every handler is up, so connectStored cannot let a fast connected state land with nobody listening, and bindEvents still hands back the transport's unlisten and still rejects only when the transport is the one that failed. src/lib/boot.js is the project's first launch instrument — one counter every invoke and every listen passes, sealed at the first frame the shell paints and never re-opened, so a probe landing a second and a half later still reads the launch. perf.mjs measures frames; nothing measured the launch. The mock walk's Home step now holds a budget against it and both walks print bootCalls, bootSync, bootMs and bootLibMs on every step. bootSync is the clause with no clock in it: a call that waits on the one before it can only issue its first in the shell's own turn, so a chain put back in a queue falls under the floor whatever the machine is doing. A bootCalls of -2 means __ss.boot is not there at all, which is the census itself being broken rather than the app. One import line goes with it: Sheet.svelte's `import * as api`, the only one of the ten dead imports this round turned up whose reason is the bundle at all. Rollup already drops an unreferenced NAMED import, so a dead name on an import line is hygiene rather than bytes; a namespace import is the one shape tree-shaking cannot see through, and this one was dead. It frees nothing on its own — twenty-nine other files in the phone's graph hold the same module the same way, so api.js is pinned into the chunk whatever Sheet.svelte does — but it is the only one of the ten that belongs to a round about weight. The nine that are drift go out with the sweep in the round after this one. Two things were looked at and left alone, both written down in ANDROID.md rather than half-done. Home's per-artist catalog walk is the deepest chain in the app, and that serial walk is the Deezer and MusicBrainz pacing, not an oversight. And the follows really are read twice at launch, but the obvious guard does not work: Home's effect calls loadHome with force the first time they land, so the guard would never fire. One small local read, left for a round that can do it properly.
This commit is contained in:
1 parent
052e15302b
commit
01bab007f7
10 files changed
+304
-43
No files matched your search
+81
-1
@@ -782,6 +782,82 @@ switch (You → Appearance → Glass blur). Re-run the probe before adding any
|
||||
backdrop blur to the phone; `--quick` runs the base variant only, and the
|
||||
app must be on the phone's screen.
|
||||
|
||||
The launch itself had never been measured. The bundle the phone loads is
|
||||
`mobile.html` and three chunks: its own (about 300 KB), the shared one
|
||||
(about 130 KB) and a small third (about 27 KB) — roughly 460 KB of JS and
|
||||
57 KB of CSS, all of it eager. The static graph from `src/mobile-main.js`
|
||||
outward is 64 modules and contains no dynamic import at all, so there is one
|
||||
chunk and the phone evaluates every byte of it at every launch. Importing
|
||||
that graph in headless Chrome costs 11.6 ms at full speed and 59.2 ms at a
|
||||
6× CPU throttle.
|
||||
|
||||
The desktop is not riding along, which was checked rather than assumed: of
|
||||
`state.svelte.js`'s 44 long string literals exactly four appear in the
|
||||
chunks the phone loads, and all four are event names it legitimately uses.
|
||||
`heroGpu.js`, `trace.js`, `ImportWindow.svelte`, `TitleBar` and
|
||||
`DebugOverlay` contribute nothing; there is no shader source anywhere in the
|
||||
bundle. The one passenger is the dev mock library
|
||||
(`src/lib/mockLibrary.js`, about 32 KB of the shared chunk), which
|
||||
`src/lib/api.js` and `src/lib/events.js` import unconditionally because
|
||||
`mockInvoke` has to answer synchronously. It costs 6.6 ms of every launch at
|
||||
6× throttle, only 1.4 ms of which is its top-level work — the rest is parse
|
||||
and function definition, which only removing it from the bundle can recover.
|
||||
`import.meta.env.DEV` cannot do that, because the mock walk builds
|
||||
`dist-smoke/` with a plain production `vite build`; getting it out needs a
|
||||
build-mode flag the walk itself would have to pass. Named here rather than
|
||||
taken.
|
||||
|
||||
The boot chain was the expensive half. It is 26 commands the census can see,
|
||||
plus two plugin listeners that register through `@tauri-apps` directly and go
|
||||
past it. Until this round its critical path — the longest chain where each
|
||||
call was issued only after the one before it had answered — was ten deep,
|
||||
because `listen` is an invoke like any other: `loadLibrary` asked for five
|
||||
independent SQLite reads one after another, `bindSlsk` registered eight
|
||||
listeners one after another, and `watchLibrary` and the player's `bindEvents`
|
||||
each did two. They go out in one round now, and the difference is countable
|
||||
rather than felt: ten of those commands used to leave in the shell's own
|
||||
first turn and twenty-three do now, and the critical path is three instead
|
||||
of ten. Measured in a rig that runs the real store modules —
|
||||
both trees, the same onMount script — with the bridge instrumented and the
|
||||
backend's work modelled behind the library's single `conn: Mutex<Connection>`:
|
||||
the same commands both ways, and `lib.loaded` 85 ms → 53 at a 6 ms bridge
|
||||
round trip, 105 → 57 at 12 ms, 148 → 62 at 20 ms. Those are a desktop rig's
|
||||
milliseconds with the round trip swept rather than measured; the structural
|
||||
half holds anywhere, and the phone's own numbers arrive the first time the
|
||||
walk prints `bootMs` and `bootLibMs`.
|
||||
|
||||
Three things were looked at and deliberately left alone. Home's per-artist
|
||||
catalog walk is the deepest chain in the app — `catalog_artist` →
|
||||
`catalog_artist_identity` → `catalog_related`, serially, per followed artist
|
||||
— and that is the pacing, not an oversight: `crates/metadata/src/lib.rs`
|
||||
holds a Deezer budget of 45 requests per 5 s and records the MusicBrainz
|
||||
identity chain at 4–6 requests behind a mandatory 1 req/s gate, 3.7–8.5 s
|
||||
per artist. The follows are read twice at launch (once by `loadLibrary`,
|
||||
once by `loadHome`), and the obvious guard does not work, because Home's
|
||||
effect calls `loadHome(true)` the first time the follows land — one small
|
||||
local read, left for a round that can do it properly. And lazy-loading the
|
||||
one-tab screens (Diagnostics, Import, Browse, Peer, Room, Thread and the
|
||||
importer) is 9% of the source against a real risk of a blank pushed frame,
|
||||
so it is worth about 6 ms at 6× throttle and is not worth a dynamic import
|
||||
yet.
|
||||
|
||||
The instrument is `src/lib/boot.js`: one counter every `invoke` and every
|
||||
`listen` passes, sealed at the first frame the shell paints and never
|
||||
re-opened, so a probe landing a second and a half later still reads the
|
||||
launch. The mock walk holds a budget against it at the Home step and both
|
||||
walks print `bootCalls`, `bootSync`, `bootMs` and `bootLibMs` on every step.
|
||||
`bootSync` is the clause with no clock in it: it is how many of the boot
|
||||
commands left in the shell's own turn, so a chain put back in a queue can
|
||||
only fall under its floor, whatever the machine is doing. Three things the
|
||||
instrument does not do. It counts the app's own traffic, so the media card's
|
||||
and the transfer notice's `addPluginListener` are invisible to it. It counts
|
||||
commands, not bytes, so a later round that adds an import to a shared file
|
||||
can move modules between chunks with nothing failing — that gap is open. And
|
||||
the mock's exact count depends on how much of the first screen has asked for
|
||||
its covers by the time the second frame lands, so the call ceiling is set to
|
||||
catch a regression in kind (a per-artist walk or an index pulled to boot)
|
||||
rather than a handful of calls.
|
||||
|
||||
The audio side had its own lag: `dumpsys media.audio_flinger` showed the
|
||||
track on the fast mixer with runs of ~55 ms underruns. The 8192-frame buffer
|
||||
above moved it to the normal mixer; the phone walk samples the underrun
|
||||
@@ -917,7 +993,11 @@ of theirs streamed → a buddy's sheet → You (every switch and chip saved
|
||||
and read back from the config) → recent searches and a spelling
|
||||
suggestion → the sleep timer → the phone turned sideways (the player's three faces, a
|
||||
record with a long sheet over it, an artist page, and the side insets faked,
|
||||
all at 915×411) and back, asserts what each step should show, screenshots every step into `tests/mobile/out/`, and fails on
|
||||
all at 915×411) and back, asserts what each step should show — the Home
|
||||
step holds the launch to a budget as well, reading off `__ss.boot` how many
|
||||
commands the app issued before its first frame, how many of those left in the
|
||||
shell's own turn, how long it took, and requiring round 12's art prewarm to
|
||||
still be absent from them — screenshots every step into `tests/mobile/out/`, and fails on
|
||||
any exception or console error. The block at the end of the mock walk is the
|
||||
weather: a library index that will not read and its Try again, the client taken
|
||||
offline, a refused password and a server that cannot be reached, Search with
|
||||
|
||||
Reference in new issue
Block a user