diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-21 23:05:00 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 20b39924ceabc13c1c72fbeccdcf7f3d490228db (patch) | |
| tree | 59fd2e450689543864ae3f8dca114c229bfe6f44 /features.txt | |
| parent | 5dcfade5f102256de787b2157b01293160780411 (diff) | |
| download | pardes-20b39924ceabc13c1c72fbeccdcf7f3d490228db.tar.gz pardes-20b39924ceabc13c1c72fbeccdcf7f3d490228db.zip | |
Measure startup, and find there is nothing in it worth optimizing
A --startup case in the perf harness times Pardes.init and the first frame for
each boot layout, with the same --json and --base shape as the others, so these
are a baseline rather than a one-off reading. The core boots and paints in
0.22 ms (tty) to 0.30 ms (classic): about one percent of what starting the
editor costs, and nothing to win.
The other 99% is one thing, found with perf record rather than guessed at.
Zig's start.main allocates through the Debug-mode DebugAllocator, which
captures a six-frame stack trace per allocation, and the first capture parses
and sorts the DWARF unwind tables of an 800 MB binary -- 22% of all samples sit
in mem.swap under that pdq sort. It is not the dynamic loader (25 us), not
static initializers (the binary has no .init_array), not paging (435 page
faults), and not lockStderr. std/start.zig:694 hardcodes DebugAllocator(.{}),
so there is no knob short of the build mode.
Controls that pin it down: a bare std.process.Init hello-world starts in 3 ms,
the ReleaseFast pardes-perf binary in 4 ms, /bin/true in 1 ms, and a Debug
pardes in 22 ms. The gui binary's 107 ms first run was cold page cache; warm it
matches the tty one.
So the conclusion recorded in features.txt is to leave it alone. Twenty-two
milliseconds is imperceptible for an editor, and the only lever is switching
the default build mode, which would multiply an already ten-minute build to
save eighteen milliseconds of startup.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Diffstat (limited to 'features.txt')
| -rw-r--r-- | features.txt | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/features.txt b/features.txt index a09607ea..9933dd14 100644 --- a/features.txt +++ b/features.txt @@ -73,3 +73,42 @@ harness, and the baseline files are how a regression gets caught later. object and reading the fid only tells you which one you got; acme's new/ctl is the same shape, and pardes's old /new already keyed its side effect on open. The Tcreate that replaced it has a real wart: a pane is named by a server-assigned serial, so the create ignores the client's name and `mkdir /pane/foo` leaves you /pane/12. Replace it with /pane/clone (open allocates, read answers the serial) and keep Tremove, which is unambiguously right. + +TODO, deferred: ligatures on the sdl backend. macOS got them with the first-class-host commit because CoreText shapes for free; sdl rasterises one glyph per +codepoint through FreeType (src/gui/font.c calls FT_Get_Char_Index then FT_Load_Glyph) and FreeType does no shaping, so this means adding HarfBuzz. It is not the +whole renderer, but it is most of the text path: the glyph atlas is keyed by codepoint (GlyphKey{codepoint, role, decoration}) and a ligature has no codepoint, so +the cache rekeys to glyph indices; a shaping stage has to run over runs of same-styled text within a row; and the per-cell instance emit has to place one glyph +across several columns and remember which columns a ligature consumed (macOS does this with ligatureShaped/ligatureCovered). The GPU batching, atlas upload and +scene shaders are untouched. Not worth it until something else wants a shaper. + +Not a bug: taglines sit one pixel bottom-heavy when cell_h - tagline_h is odd, because taglineBandOffset splits the spare with a truncating /2. Confirmed +acceptable; leave it. + +Startup, measured before optimizing (zig build perf -- --startup, plus timings of the installed binaries): + core Pardes.init + first frame 0.22 ms (tty) .. 0.30 ms (classic) -- negligible + dynamic linker ~25 us, 346 relocations, 4 libs -- negligible + page faults for `pardes --version` 435 -- the 812 MB binary is never paged in, size is a red herring + pardes --version 22 ms real, 21 ms USER, 1 ms sys + pardes-gui --version 107 ms real, 23 ms user -- ~84 ms blocked, not CPU + /bin/true control 1 ms real, 0 user -- the harness costs nothing +Cause found, by profiling rather than guessing (perf record on `pardes --version`): + + start.main -> mem.Allocator.alloc + -> heap.debug_allocator.DebugAllocator(.{ .stack_trace_frames = 6, ... }).alloc + -> collectStackTrace -> debug.captureCurrentStackTrace + -> SelfInfo.Elf.unwindFrame -> loadUnwindSections -> Dwarf.Unwind.prepare + -> mem.sortUnstable (22% of all samples sit in mem.swap under the pdq sort) + +Zig's own start.main allocates through the Debug-mode DebugAllocator, which captures a 6-frame trace on every allocation, +and the first capture has to parse AND SORT the DWARF unwind tables of an 800 MB binary. That is the whole 18 ms. It is +not lockStderr (added to a hello-world: free), not static initializers (the binary has no .init_array), not the dynamic +loader (25 us), and not paging (435 page faults). std/start.zig:694 hardcodes `DebugAllocator(.{})`, so there is no knob. + +Controls: a bare std.process.Init hello-world is 3 ms; the ReleaseFast pardes-perf binary (81 MB, links libc, so +use_debug_allocator is false) starts in 4 ms; /bin/true is 1 ms. The gui binary's one-off 107 ms first run was cold page +cache, not blocking -- warm it is 25 ms, the same as the tty one. + +Recommendation: do NOT chase this. 22 ms warm is imperceptible for an editor, the only lever is the build mode, and +switching the default from Debug to ReleaseSafe would multiply an already ~10 minute build to save 18 ms of startup -- +a bad trade when the build is the actual bottleneck in this project. Worth knowing for the day a binary ships to someone +else: ReleaseSafe keeps the safety checks, drops the debug allocator, and is 5x faster to start and 10x smaller. |
