client: Linux startup trails alacritty by 70 ms, and a cold macOS launch beats a warm one #402
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#402
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Time to the first window (
docs/reference/benchmarks.md, felis9d1d842f, ms):Two things to explain:
Done when each step's cost is known, the overlap that is available is taken, and the macOS inversion is explained.
macOS: the "warm" row never had a warm daemon, and the window reaches the screen only after GPU and font init
The cold/warm inversion
On macOS the harness's warm leg times cold starts. Both published rows measure the same client path.
tools/bench/suites.py:635(stop_launch) killsfieldmod.process_tree(proc.pid).felis-client(crates/felis-client-core/src/spawn.rs:62-63). The systemd hand-off above it is Linux-only, so the daemon is in that tree, and stopping each launch also kills the daemon it spawned.clear_sessions(suites.py:525) runsfelis sessions list, which does not autospawn (Dial::Ops,RemoteSpawn::Refuse). The next "warm" launch therefore finds no daemon and spawns one.I replicated the harness procedure: same
felis_home,exec sleep 60wrapper,MacWatcher,stop_launch,kill_daemonandclear_sessions. I added a third mode whose daemon starts outside any launch tree (felis-daemon servewithstart_new_session). Setup: maina577fee0with a local probe patch, 10 timed launches after 1 discarded, screen locked, display asleep, load 2.15 → 1.60.An earlier batch of 5 timed runs (load about 1.8) gave "warm" 123.6 ms and real warm 117.6 ms. Four of its six cold runs took 8.5-9.3 s, with 6 s in font discovery and about 1 s each in event-loop build and
resumed. That happened with the display asleep and I did not pursue it; the harness's 3 discarded launches would absorb it.Tonight the two harness rows differ by 6 ms with overlapping ranges, so the published 18 ms gap (122-135 against 144-149 in all three rounds) did not reproduce. Whatever caused it, it sits outside the client: the client timeline of the two rows matches to 0.5 ms (below). One candidate is the extra
felis sessions listprocess just before each "warm" launch; I have not verified it.Where the warm path spends its time
Probe timestamps are wall time from spawning
felis, medians of 10 runs:felis→felis-clientmaindial_launch(connect + handshake; cold: spawn + boot retry)create_with)EventLoopbuilt (NSApplication init)resumeddeliveredcreate_windowreturnedrenderreturnedcreate_windowreturns (event_handler.rs:44). In the medians it appears about 20-35 ms after the first render, onceresumedhas returned to AppKit. The GPU setup, font discovery and first render (about 23 ms) are therefore inside the macOS metric, although the docs describe the metric as "ordered on screen, not drawn into".resumed, andcreate_windowtake about 57 ms.Proposed fix
stop_launch's kill set for felis. Assert that it is alive before each warm launch and record that in the leg JSON. The cold leg needs no change.FontStack::auto_discover(felis-render-wgpu/src/lib.rs:499) on a thread inmain, so it overlaps dial/attach and NSApplication init. The wgpu instance, adapter and device can also be requested before the window exists. On macOS this saves at most about 15 ms of about 116 ms (fonts 12.3, GPU 3), becauseresumedgates the window's appearance.main.rs:793-795(dial, including the daemon spawn/hand-off, and attach) runs strictly beforemain.rs:887(event loop) and everything after it. Event loop, window, GPU and fonts do not depend on the session, so they could run while the daemon boots. Only the attach's dims and the first GridFrame are needed before the first paint.Linux part (kilimanjaro, i5-12400F, NVIDIA RTX 595.99 driver, niri, scale 1). I timed the client's path with a local build of
mainef96bb46that adds oneinfo!("STARTUP-STEP …")log at each step boundary (not committed). Time zero is just beforeexecoffelis, and "window mapped" is the moment niri's event stream reported the client's toplevel. Default config with an empty$HOME(a config namingDejaVu Sans Monochanges nothing: font discovery takes 2.8 ms either way). Load 3.5-4.7.Where the time goes (ms from exec; three runs each)
felis→ execfelis-client, dynamic loadingconnect_or_spawn_daemon,create_withEventLoop::buildcreate_windowInstance::new+create_surfacerequest_adapterrequest_devicesurface.configure+ pipelinesFontStack::auto_discover,GlyphCache::new,ImageAtlas::newpresentThose runs pinned the NVIDIA ICD alone (
VK_DRIVER_FILES, needed because of the driver mismatch described in #398). The published run had every ICD the system ships (14 manifests, lavapipe among them). Pinning all 14 reproduces that condition:That matches the published warm 174 ms. Loading every installed Vulkan driver at
vkCreateInstancecosts 35-50 ms on top of loading NVIDIA's alone.Answers
run_client(crates/felis-client/src/main.rs:785-797) dials and attaches beforedrivebuilds the event loop, andRenderer::new_with_config(crates/felis-render-wgpu/src/lib.rs:326) startsInstance::newonly insideresumed, after the window exists. None of the instance, adapter or device work needs the window or the daemon. Onlycreate_surfaceandsurface.configureneed the window, and the adapter can be requested withoutcompatible_surfaceand checked withadapter.is_surface_supported(&surface)afterwards. Starting instance + adapter + device on a thread at the top ofmain, in parallel with config load, the daemon dial, the Wayland connect and the window, would hide:Font discovery (3 ms) and atlas creation can run beside the device request too (about −3 ms).
Proposed fix, ordered by expected gain: (1) start the GPU instance, adapter and device at process start on a helper thread, and join it in
resumed: −30 ms cold, −10 ms warm. (2) Nothing felis should do about the ICD count; that cost belongs to the system's Vulkan loader. It is worth recording in the benchmark notes that the Linux startup figure moves by 35-50 ms with the installed driver set.The macOS inversion is for the macOS lane.