Repository navigation
perf 5/5: startup on worker threads (first frame 82→49 ms), binary −1 MB - #65
Merged
Merged
Conversation
…e bundled font Buffer::new shapes an empty line with Attrs::new() (sans-serif 400), and the first shape of an Attrs makes cosmic-text rank every installed face against it, opening each variable font for its weight axis: ~10 ms with ~1000 system faces, on the startup critical path, for Attrs we never shape with. Use Buffer::new_empty: every buffer gets set_text before it is shaped. Also hand fontdb the bundled font's static bytes instead of a 170 KB heap copy.
Launch to first frame was a serial chain on the main thread: winit opens the display and builds the event loop (~20-30 ms on X11), creates the window, then the renderer creates the wgpu instance, adapter and device (~20 ms, mostly loading the Vulkan driver), and the rasterizer scans and parses every system font and ranks the faces for the configured family (~35 ms). The GPU and font work need no window, so start them on two worker threads at the top of main (toastty::preload) and join them in init_impl, by which time they are usually done. toastty-render grows the pieces for that: - GpuContext: instance + adapter + device without a surface. Renderer::with_gpu keeps its adapter only if it can present to the window's surface, else selects one for the surface exactly as Renderer::new does (new is now with_gpu(None, ..)). - LoadedFonts / load_fonts: the size- and GPU-independent half of GlyphRasterizer (font db, resolved family weight, availability), with the primary family's face ranking warmed. Renderer::with_loaded_fonts and GlyphRasterizer::with_fonts consume it; with_font_ex / new load inline as before. Any worker failure or panic falls back to the inline path, so only the timing changes. Launch->first frame on Xvfb: 112 -> 65 ms (p50 of 15).
The pipelines need only the device and surface format. Renderer:: prepare_pipelines builds them (and a spare text pipeline that the next with_loaded_fonts takes), and the binary calls it before joining the font preload, so ~5 ms of shader compilation overlaps the wait for the font database instead of following it.
glibc keeps freed heap pages; only the very top of the main heap is returned automatically. Startup frees a lot (the placeholder-size grid replaced once the cell size is known, font-scan and driver temporaries on their threads), so an idle terminal held ~11 MB of free heap. Call malloc_trim(0) once, after the first frame (~1 ms; a no-op off glibc). Idle RssAnon 52.3 -> 40.7 MB.
The renderer only requests Backends::PRIMARY (Vulkan/Metal/DX12), so the gles backend (glow, the EGL loader, wgpu-hal's GL code) was linked but unreachable. Stripped binary 24.78 -> 24.14 MB.
mermaid-svg, ratex-*, usvg, svgtypes, pulldown-cmark, gltf(-json), tobj, toml(_edit), winnow, petgraph: run once per block, asset or config load. Rendering the demo Markdown document (Mermaid + maths) is unchanged within noise (median 228-244 ms either way). Stripped binary 24.14 -> 23.70 MB.
If startup fails before init_impl takes the preloaded GPU context or fonts, wait for the workers rather than exiting while one is still inside driver or fontconfig setup.
It runs on the main thread before the startup workers are spawned, so it is on the startup path the table's comment promises to leave at 3.
# Conflicts: # crates/toastty-render/src/lib.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack 5/5. Based on #64. It covers startup, idle memory and binary size. Launch → first frame drops from about 82 ms to 49 ms. The rendered output and behaviour are unchanged.
What changed
toastty::preload).Preload::spawnstarts both at the top ofmainandinit_impljoins them.None, and the old inline path runs, so only the timing changes.toastty-renderpieces for that.GpuContextholds an instance, adapter and device without a surface.Renderer::with_gpukeeps the pre-picked adapter only if it can present to the window's surface. Otherwise it selects one exactly asRenderer::newdoes, andnewis nowwith_gpu(None, ..).LoadedFonts/load_fontsis the GPU- and size-independent half ofGlyphRasterizer.Renderer::prepare_pipelines, about 5 ms overlapped).Buffer::new_emptyskips cosmic-text ranking every installed face against a defaultAttrswe never shape with (about 10 ms with about 1000 system faces). The bundled font is handed to fontdb as static bytes, not a 170 KB copy.malloc_trim(0)once after the first frame (glibc only, a no-op elsewhere). On the base this commit was written against, it cut idle anonymous RSS from 52 to 41 MB. On top of perf 3/5: parser + grid hot path — 2–7× parse, lazy scrollback (idle RSS 101→60 MB) #63's lazy scrollback, startup frees much less, so the saving is now within noise (table below).Backends::PRIMARY(Vulkan/Metal/DX12), so no machine loses a working path.opt-level = "s"for cold, code-heavy dependencies that never run per frame or per PTY byte: Mermaid, LaTeX maths, SVG tree building, pulldown-cmark, glTF/OBJ, petgraph. Rendering the demo Markdown document measured the same.Preload'sDropjoins untaken workers, so a failed startup never exits while a worker is inside driver or fontconfig setup.Review
An independent reviewer found no correctness bugs. Things it confirmed:
--help,--print-default-config, CLI errors) returns before the workers start.Two small findings are fixed in the last two commits:
toml,toml_editandwinnowwere at"s", but the config parse runs on the main thread before the workers start.Left as is:
malloc_trimtiming. It runs inline after the first frame (about 1 ms). It could move to the first idle point.XOpenDisplay. No known driver does this; Mesa uses xcb.with_gpufallback (pre-picked adapter can't present to the surface) has no test. Wayland and macOS were not run; Xvfb only covers X11.Numbers
bench/run.pyon Xvfb, 3 interleaved reps, median, compared with #64. Binaries were copied to disk; tmpfs copies misreport RSS.The cumulative numbers for the whole stack against
mainare posted as a comment on this PR.Visual evidence
The test scene (scrollback, SGR attributes, truecolor and 256-colour, CJK, emoji with ZWJ and skin tone, combining marks, tabs, absolute positioning, a wrapped line), captured from the #64 build and from this branch.
magick compare -metric AEreports 0 differing pixels. It is also identical to the #63 capture.Before (#64):
After (this branch), 0 differing pixels:
Testing
cargo test --workspace --no-fail-fast: 2431 passed, 0 failed.cargo clippy --workspace --all-targets: no new warnings compared with perf 4/5: renderer — 2.7× cheaper full-frame build, memoised row shapes #64.Not done