detached b807617

docs(performance): record dated allocation and memory measurements

commit
b80761739aad786fc5361c960962eff4975b2a8b
author
brice · · 2026-09-30 04:19 UTC
parent
7e46072
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +55 -29
M docs/performance.md +55 -29
@@ -2,53 +2,79 @@
22
33 ## CPU budgets
44
5-AMD Ryzen 9 7900X3D 12-Core Processor; `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets are approximately twice the measured medians (zero allocations remain zero). CPU numbers vary with machine load. The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup; Prepare uses a representative warm text/box command mix and excludes GPU submission.
5+Measured: **2026-09-30, 06:14–06:16 CEST (UTC+02:00)**. Implementation commit: **`7e46072588c8dce90eaa8bc11f89a5564f182b04`**.
66
7-| Package / benchmark | Median ns/op | Median B/op | Median allocs/op | Budget ns/op | Budget B/op | Budget allocs/op |
8-|---|---:|---:|---:|---:|---:|---:|
9-| internal/ui / DemoFrame | 21,238 | 32,816 | 220 | 45,000 | 66,000 | 500 |
10-| render / Prepare | 59,024 | 158,720 | 386 | 125,000 | 320,000 | 800 |
11-| text / AtlasWarmLookup | 29.07 | 0 | 0 | 60 | 0 | 0 |
12-| text / RasterDistinct1000 | 4,139,638 | 8,566,137 | 16,784 | 8,300,000 | 17,200,000 | 34,000 |
13-| text / ShapeParagraph (uncached) | 226,223 | 300,146 | 950 | 460,000 | 600,000 | 1,900 |
14-| text / ShapeParagraphCached | 78.16 | 0 | 0 | 160 | 0 | 0 |
15-| text / AtlasInsert | 508.3 | 966 | 3 | 1,050 | 2,000 | 6 |
16-| css / Engine/cold | 2,310,093 | 1,956,258 | 5,608 | 4,700,000 | 4,000,000 | 11,300 |
17-| css / Engine/warm | 95,553 | 1 | 0 | 195,000 | 2 | 0 |
18-| css / Engine/state-change | 334,293 | 149,130 | 363 | 680,000 | 300,000 | 750 |
19-| layout / Layout1000 | 623,154 | 731,626 | 1,027 | 1,250,000 | 1,470,000 | 2,100 |
7+AMD Ryzen 9 7900X3D; Go `go1.27.1-X:nodwarf5 linux/amd64`. Command: `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets allow roughly twice the measured medians; zero-allocation paths stay at zero. CPU numbers vary with machine load.
208
21-`TestDemoFrameAllocationBudget` checks the 500 allocs/op limit with `testing.AllocsPerRun`.
9+The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup. Prepare uses a representative text/box command mix and excludes GPU submission. Paint uses 100 cards with two shadows and text; ListBatches uses 2,000 quads with interleaved images. ListBufferReuse includes initial buffer allocation amortized over the run, so B/op is not a steady-state allocation cost.
2210
23-Frame construction stays low-allocation because:
11+| Package / benchmark | Median ns/op | Median B/op | Median allocs/op | Budget ns/op | Budget B/op | Budget allocs/op |
12+|---|---:|---:|---:|---:|---:|---:|
13+| internal/ui / DemoFrame | 25,865 | 39,272 | 193 | 55,000 | 80,000 | 500 |
14+| render / Prepare | 49,142 | 85,568 | 379 | 100,000 | 175,000 | 800 |
15+| render / PrepareBoxes | 67,026 | 229,377 | 1 | 140,000 | 460,000 | 2 |
16+| render / ListBatches | 112,291 | 420,571 | 2 | 230,000 | 850,000 | 4 |
17+| render / ListBufferReuse | 63,299 | 69 | 0 | 130,000 | 150 | 0 |
18+| layout / Paint | 115,660 | 479,360 | 4 | 240,000 | 960,000 | 8 |
19+| layout / LayoutPaintTree | 186,338 | 552,820 | 343 | 380,000 | 1,110,000 | 700 |
20+| layout / Layout1000 | 664,176 | 731,684 | 1,027 | 1,350,000 | 1,470,000 | 2,100 |
21+| text / AtlasWarmLookup | 31.84 | 0 | 0 | 65 | 0 | 0 |
22+| text / RasterDistinct1000 | 4,187,468 | 8,566,132 | 16,784 | 8,500,000 | 17,200,000 | 34,000 |
23+| text / ShapeParagraph (uncached) | 245,802 | 300,149 | 950 | 500,000 | 610,000 | 1,900 |
24+| text / ShapeParagraphCached | 79.34 | 0 | 0 | 160 | 0 | 0 |
25+| text / AtlasInsert | 564.9 | 968 | 3 | 1,150 | 2,000 | 6 |
26+| css / Engine/cold | 2,358,611 | 1,964,619 | 5,669 | 4,800,000 | 4,000,000 | 11,400 |
27+| css / Engine/warm | 100,682 | 1 | 0 | 205,000 | 2 | 0 |
28+| css / Engine/state-change | 348,826 | 149,107 | 363 | 700,000 | 300,000 | 750 |
29+
30+`TestDemoFrameAllocationBudget` checks the 500 allocs/op limit with `testing.AllocsPerRun`. Focused tests also check constant quad allocation, bounded paint allocation, allocation-free warm list-buffer reuse, and allocation-free idle wait results. The other table budgets are reference targets, not automated assertions.
31+
32+Frame construction limits allocation churn through:
33+
34+- one pre-sized quad array per prepared frame, preserving independent frame lifetimes;
35+- detached display-list storage sized once for commands, shadows, runs and glyphs;
36+- a session-owned instance/batch buffer, consumed synchronously before reuse, with image references cleared after use and oversized storage discarded when smaller frames arrive;
37+- font family normalization at font load and identity path keys computed once per element;
38+- `text.Engine.Measure` caching by text, request and width. Entries unused for two frames are evicted; the cache holds at most 4,096 entries. Results are immutable; callers use `Layout.Clone` before adjusting positions.
39+
40+`Run` builds a frame only on input or a redraw request. Idle frame construction stops. Expected release-wait timeouts return without constructing errors; real ioctl failures retain their context and wrapped errno.
41+
42+On the same implementation commit, measured **2026-09-30, 06:15–06:16 CEST**, `BenchmarkWaitPointIdle` on an unsignaled real DRM timeline took a median **21,567,553 ns/op, 0 B/op, 0 allocs/op**. This includes the requested 20 ms timeout, not active CPU time:
2443
25-- font family names are normalized once when fonts load, not per face on every match;
26-- `text.Engine.Measure` caches results by text, request and width; entries unused for two frames are evicted and the cache holds at most 4,096 entries (callers treat results as immutable and use `Layout.Clone` before adjusting positions);
27-- identity path keys are computed once per element.
44+```sh
45+NEFERGUI_RENDER_NODE=/dev/dri/renderD128 CGO_ENABLED=0 GOWORK=off \
46+ go test -run '^$' -bench BenchmarkWaitPointIdle -benchtime=10x -benchmem -count=5 ./internal/presentation/syncobj
47+```
2848
29-`Run` builds a frame only on input or a redraw request. Idle frame construction stops, but platform release-wait polling can still allocate small amounts. Profile with:
49+Profile frame construction with:
3050
3151 ```sh
3252 go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 ./internal/ui
3353 go tool pprof -sample_index=alloc_objects -top -cum mem.out
3454 ```
3555
36-A 400-frame demo harness run (960×640, scale 1, input: name Ada then continue) recorded `submit_to_commit_ns` median **184,545 ns** and nearest-rank p95 **481,550 ns** in `frame-timings.jsonl`. This is host-side submission-to-Wayland-commit latency, not GPU execution time. `atlas_pages` stayed at **1** allocated 1024×1024 grayscale page (maximum 4) for all 400 frames. These figures depend on the compositor, driver and machine load.
37-
3856 ## Memory as a dependency
3957
40-An external Go module with a minimal counter view, a 960×640 window, and a private headless NeferWL compositor was profiled on AMD/RADV. Baseline: NeferGUI `068ba85`, Go 1.27.1, Mesa/RADV 26.2.3, and NeferWL `1d16b83bf90f9e8189ad21b27772382c96b9f9a8`. A minimal-consumer repeat at `84c1f24` matched retained heap within 0.02 MiB and used identical GPU allocations. Debug readbacks were disabled. Live Go heap was sampled after forced GC; RSS includes shared driver libraries, while PSS apportions shared pages.
58+Measured: **2026-09-30, 06:12–06:14 CEST (UTC+02:00)**. Implementation commit: **`7e46072588c8dce90eaa8bc11f89a5564f182b04`**. Comparison baseline: **`1488e4a390e93603976ae3f5e9ac3fdb1bd00c1a`**, measured **2026-09-30, 06:06–06:10 CEST**.
59+
60+An external Go module with minimal-counter and demo views, a 960×640 window at scale 1, and a private headless NeferWL compositor was profiled on AMD/RADV. Stack: Go `go1.27.1-X:nodwarf5 linux/amd64`, Mesa/RADV 26.2.3, NeferWL `1d16b83bf90f9e8189ad21b27772382c96b9f9a8`. Debug readbacks were disabled. Live Go heap was sampled after forced GC; RSS includes shared driver libraries, while PSS apportions shared pages.
4161
4262 | Measurement | Minimal view | Demo view |
4363 |---|---:|---:|
44-| Live Go heap after 1,000 frames | 5.3 MiB | 5.4 MiB |
45-| Process RSS / PSS after 1,000 frames | 60 / 43 MiB | 64 / 47 MiB |
46-| Estimated temporary allocations per rendered frame, measurement sampler's own allocations excluded | about 65 KiB | about 230 KiB |
64+| Live Go heap after 1,000 frames | 5.16 MiB | 5.29 MiB |
65+| Live Go heap after 3,000 frames | 5.19 MiB | 5.31 MiB |
66+| Process RSS / PSS after 1,000 frames | 62 / 39 MiB | 58 / 39 MiB |
67+| Temporary allocations per rendered frame | about 36 KiB | about 136 KiB |
68+| Baseline temporary allocations per rendered frame | about 61 KiB | about 238 KiB |
69+
70+Heap/RSS/PSS use the default Go profiling rate. Allocation figures use separate `GODEBUG=memprofilerate=1` runs: subtract cumulative profiles at frames 1 and 1,000, exclude stacks containing the measurement sampler or `runtime/pprof`, sum the remaining flat allocated bytes, then divide by 999 frames. These runs recorded about **42–43% less allocation churn** than the baseline. They do not demonstrate an equivalent reduction in RSS or frame latency. Single-run process-memory values vary with runtime and driver behavior.
71+
72+The minimal view starts at about 0.75 MiB live Go heap, reaches 5.1 MiB after the first frame, and returns to about 0.9 MiB after closing. No sustained live-heap growth was observed over 3,000 frames. Retained heap stayed close to baseline; the session's reusable conversion buffers retain a small amount of storage. Driver mappings and Go heap capacity can remain resident after closing; RSS is not a leak measurement on its own.
4773
48-The minimal view starts at about 0.75 MiB live Go heap, reaches 5.2 MiB after the first frame, and returns to about 0.9 MiB after closing. No sustained live-heap growth was observed over 1,000–3,000 frames. Driver mappings and Go heap capacity can remain resident after closing; RSS is not a leak measurement on its own.
74+DRM counters reported about 41 MiB VRAM and 4 MiB GTT once all three presentation buffers were active, unchanged from baseline and released at shutdown. Window dimensions, scale, fonts and driver affect these figures. These are measurements on one stack, not portable limits.
4975
50-DRM counters reported about 41 MiB VRAM and 4 MiB GTT once all three presentation buffers were active. Window dimensions, scale, fonts and driver affect these figures. These are measurements on one stack, not portable limits.
76+A separate approximately 20-second idle profile recorded about **91 KiB** allocated under `syncobj.WaitPoint` in the baseline and **no allocations attributed to that path** on the measured implementation. Other runtime and consumer instrumentation allocations remain; this is not a claim that the whole process allocates nothing at idle.
5177
52-The largest frame-allocation sources are prepared quads, render batches, and layout painting. Release-wait polling allocated about 150 KiB over a 38-second idle interval. `NEFERGUI_DEBUG_DIR` enables readbacks and PNG work and raised observed peak RSS to about 113 MiB; leave it unset when measuring ordinary application cost.
78+Prepared quads and layout painting still allocate detached outputs. Glyph rasterization and glyph-key hashing also appear in cumulative allocation profiles. `NEFERGUI_DEBUG_DIR` enables readbacks and PNG work; leave it unset when measuring ordinary application cost.
5379
5480 Use `pprof`'s `alloc_space` to find cumulative allocation churn and `inuse_space` after GC to inspect retained Go memory. Use `/proc/<pid>/smaps_rollup` for RSS/PSS and DRM fdinfo for GPU counters; heap profiles do not include driver or GPU memory.
@@ -2,53 +2,79 @@
2 2
3 ## CPU budgets3 ## CPU budgets
4 4
5-AMD Ryzen 9 7900X3D 12-Core Processor; `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets are approximately twice the measured medians (zero allocations remain zero). CPU numbers vary with machine load. The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup; Prepare uses a representative warm text/box command mix and excludes GPU submission.5+Measured: **2026-09-30, 06:14–06:16 CEST (UTC+02:00)**. Implementation commit: **`7e46072588c8dce90eaa8bc11f89a5564f182b04`**.
6 6
7-| Package / benchmark | Median ns/op | Median B/op | Median allocs/op | Budget ns/op | Budget B/op | Budget allocs/op |7+AMD Ryzen 9 7900X3D; Go `go1.27.1-X:nodwarf5 linux/amd64`. Command: `CGO_ENABLED=0 GOWORK=off go test -run '^$' -bench . -benchmem -count=5` in the indicated packages. Entries are medians of five runs. Budgets allow roughly twice the measured medians; zero-allocation paths stay at zero. CPU numbers vary with machine load.
8-|---|---:|---:|---:|---:|---:|---:|
9-| internal/ui / DemoFrame | 21,238 | 32,816 | 220 | 45,000 | 66,000 | 500 |
10-| render / Prepare | 59,024 | 158,720 | 386 | 125,000 | 320,000 | 800 |
11-| text / AtlasWarmLookup | 29.07 | 0 | 0 | 60 | 0 | 0 |
12-| text / RasterDistinct1000 | 4,139,638 | 8,566,137 | 16,784 | 8,300,000 | 17,200,000 | 34,000 |
13-| text / ShapeParagraph (uncached) | 226,223 | 300,146 | 950 | 460,000 | 600,000 | 1,900 |
14-| text / ShapeParagraphCached | 78.16 | 0 | 0 | 160 | 0 | 0 |
15-| text / AtlasInsert | 508.3 | 966 | 3 | 1,050 | 2,000 | 6 |
16-| css / Engine/cold | 2,310,093 | 1,956,258 | 5,608 | 4,700,000 | 4,000,000 | 11,300 |
17-| css / Engine/warm | 95,553 | 1 | 0 | 195,000 | 2 | 0 |
18-| css / Engine/state-change | 334,293 | 149,130 | 363 | 680,000 | 300,000 | 750 |
19-| layout / Layout1000 | 623,154 | 731,626 | 1,027 | 1,250,000 | 1,470,000 | 2,100 |
20 8
21-`TestDemoFrameAllocationBudget` checks the 500 allocs/op limit with `testing.AllocsPerRun`.9+The demo-frame benchmark builds a headless home-view tree, computes layout and produces a display list after warmup. Prepare uses a representative text/box command mix and excludes GPU submission. Paint uses 100 cards with two shadows and text; ListBatches uses 2,000 quads with interleaved images. ListBufferReuse includes initial buffer allocation amortized over the run, so B/op is not a steady-state allocation cost.
22 10
23-Frame construction stays low-allocation because:11+| Package / benchmark | Median ns/op | Median B/op | Median allocs/op | Budget ns/op | Budget B/op | Budget allocs/op |
12+|---|---:|---:|---:|---:|---:|---:|
13+| internal/ui / DemoFrame | 25,865 | 39,272 | 193 | 55,000 | 80,000 | 500 |
14+| render / Prepare | 49,142 | 85,568 | 379 | 100,000 | 175,000 | 800 |
15+| render / PrepareBoxes | 67,026 | 229,377 | 1 | 140,000 | 460,000 | 2 |
16+| render / ListBatches | 112,291 | 420,571 | 2 | 230,000 | 850,000 | 4 |
17+| render / ListBufferReuse | 63,299 | 69 | 0 | 130,000 | 150 | 0 |
18+| layout / Paint | 115,660 | 479,360 | 4 | 240,000 | 960,000 | 8 |
19+| layout / LayoutPaintTree | 186,338 | 552,820 | 343 | 380,000 | 1,110,000 | 700 |
20+| layout / Layout1000 | 664,176 | 731,684 | 1,027 | 1,350,000 | 1,470,000 | 2,100 |
21+| text / AtlasWarmLookup | 31.84 | 0 | 0 | 65 | 0 | 0 |
22+| text / RasterDistinct1000 | 4,187,468 | 8,566,132 | 16,784 | 8,500,000 | 17,200,000 | 34,000 |
23+| text / ShapeParagraph (uncached) | 245,802 | 300,149 | 950 | 500,000 | 610,000 | 1,900 |
24+| text / ShapeParagraphCached | 79.34 | 0 | 0 | 160 | 0 | 0 |
25+| text / AtlasInsert | 564.9 | 968 | 3 | 1,150 | 2,000 | 6 |
26+| css / Engine/cold | 2,358,611 | 1,964,619 | 5,669 | 4,800,000 | 4,000,000 | 11,400 |
27+| css / Engine/warm | 100,682 | 1 | 0 | 205,000 | 2 | 0 |
28+| css / Engine/state-change | 348,826 | 149,107 | 363 | 700,000 | 300,000 | 750 |
29+
30+`TestDemoFrameAllocationBudget` checks the 500 allocs/op limit with `testing.AllocsPerRun`. Focused tests also check constant quad allocation, bounded paint allocation, allocation-free warm list-buffer reuse, and allocation-free idle wait results. The other table budgets are reference targets, not automated assertions.
31+
32+Frame construction limits allocation churn through:
33+
34+- one pre-sized quad array per prepared frame, preserving independent frame lifetimes;
35+- detached display-list storage sized once for commands, shadows, runs and glyphs;
36+- a session-owned instance/batch buffer, consumed synchronously before reuse, with image references cleared after use and oversized storage discarded when smaller frames arrive;
37+- font family normalization at font load and identity path keys computed once per element;
38+- `text.Engine.Measure` caching by text, request and width. Entries unused for two frames are evicted; the cache holds at most 4,096 entries. Results are immutable; callers use `Layout.Clone` before adjusting positions.
39+
40+`Run` builds a frame only on input or a redraw request. Idle frame construction stops. Expected release-wait timeouts return without constructing errors; real ioctl failures retain their context and wrapped errno.
41+
42+On the same implementation commit, measured **2026-09-30, 06:15–06:16 CEST**, `BenchmarkWaitPointIdle` on an unsignaled real DRM timeline took a median **21,567,553 ns/op, 0 B/op, 0 allocs/op**. This includes the requested 20 ms timeout, not active CPU time:
24 43
25-- font family names are normalized once when fonts load, not per face on every match;44+```sh
26-- `text.Engine.Measure` caches results by text, request and width; entries unused for two frames are evicted and the cache holds at most 4,096 entries (callers treat results as immutable and use `Layout.Clone` before adjusting positions);45+NEFERGUI_RENDER_NODE=/dev/dri/renderD128 CGO_ENABLED=0 GOWORK=off \
27-- identity path keys are computed once per element.46+ go test -run '^$' -bench BenchmarkWaitPointIdle -benchtime=10x -benchmem -count=5 ./internal/presentation/syncobj
47+```
28 48
29-`Run` builds a frame only on input or a redraw request. Idle frame construction stops, but platform release-wait polling can still allocate small amounts. Profile with:49+Profile frame construction with:
30 50
31 ```sh51 ```sh
32 go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 ./internal/ui52 go test -run '^$' -bench BenchmarkDemoFrame -benchmem -memprofile mem.out -memprofilerate=1 ./internal/ui
33 go tool pprof -sample_index=alloc_objects -top -cum mem.out53 go tool pprof -sample_index=alloc_objects -top -cum mem.out
34 ```54 ```
35 55
36-A 400-frame demo harness run (960×640, scale 1, input: name Ada then continue) recorded `submit_to_commit_ns` median **184,545 ns** and nearest-rank p95 **481,550 ns** in `frame-timings.jsonl`. This is host-side submission-to-Wayland-commit latency, not GPU execution time. `atlas_pages` stayed at **1** allocated 1024×1024 grayscale page (maximum 4) for all 400 frames. These figures depend on the compositor, driver and machine load.
37-
38 ## Memory as a dependency56 ## Memory as a dependency
39 57
40-An external Go module with a minimal counter view, a 960×640 window, and a private headless NeferWL compositor was profiled on AMD/RADV. Baseline: NeferGUI `068ba85`, Go 1.27.1, Mesa/RADV 26.2.3, and NeferWL `1d16b83bf90f9e8189ad21b27772382c96b9f9a8`. A minimal-consumer repeat at `84c1f24` matched retained heap within 0.02 MiB and used identical GPU allocations. Debug readbacks were disabled. Live Go heap was sampled after forced GC; RSS includes shared driver libraries, while PSS apportions shared pages.58+Measured: **2026-09-30, 06:12–06:14 CEST (UTC+02:00)**. Implementation commit: **`7e46072588c8dce90eaa8bc11f89a5564f182b04`**. Comparison baseline: **`1488e4a390e93603976ae3f5e9ac3fdb1bd00c1a`**, measured **2026-09-30, 06:06–06:10 CEST**.
59+
60+An external Go module with minimal-counter and demo views, a 960×640 window at scale 1, and a private headless NeferWL compositor was profiled on AMD/RADV. Stack: Go `go1.27.1-X:nodwarf5 linux/amd64`, Mesa/RADV 26.2.3, NeferWL `1d16b83bf90f9e8189ad21b27772382c96b9f9a8`. Debug readbacks were disabled. Live Go heap was sampled after forced GC; RSS includes shared driver libraries, while PSS apportions shared pages.
41 61
42 | Measurement | Minimal view | Demo view |62 | Measurement | Minimal view | Demo view |
43 |---|---:|---:|63 |---|---:|---:|
44-| Live Go heap after 1,000 frames | 5.3 MiB | 5.4 MiB |64+| Live Go heap after 1,000 frames | 5.16 MiB | 5.29 MiB |
45-| Process RSS / PSS after 1,000 frames | 60 / 43 MiB | 64 / 47 MiB |65+| Live Go heap after 3,000 frames | 5.19 MiB | 5.31 MiB |
46-| Estimated temporary allocations per rendered frame, measurement sampler's own allocations excluded | about 65 KiB | about 230 KiB |66+| Process RSS / PSS after 1,000 frames | 62 / 39 MiB | 58 / 39 MiB |
67+| Temporary allocations per rendered frame | about 36 KiB | about 136 KiB |
68+| Baseline temporary allocations per rendered frame | about 61 KiB | about 238 KiB |
69+
70+Heap/RSS/PSS use the default Go profiling rate. Allocation figures use separate `GODEBUG=memprofilerate=1` runs: subtract cumulative profiles at frames 1 and 1,000, exclude stacks containing the measurement sampler or `runtime/pprof`, sum the remaining flat allocated bytes, then divide by 999 frames. These runs recorded about **42–43% less allocation churn** than the baseline. They do not demonstrate an equivalent reduction in RSS or frame latency. Single-run process-memory values vary with runtime and driver behavior.
71+
72+The minimal view starts at about 0.75 MiB live Go heap, reaches 5.1 MiB after the first frame, and returns to about 0.9 MiB after closing. No sustained live-heap growth was observed over 3,000 frames. Retained heap stayed close to baseline; the session's reusable conversion buffers retain a small amount of storage. Driver mappings and Go heap capacity can remain resident after closing; RSS is not a leak measurement on its own.
47 73
48-The minimal view starts at about 0.75 MiB live Go heap, reaches 5.2 MiB after the first frame, and returns to about 0.9 MiB after closing. No sustained live-heap growth was observed over 1,000–3,000 frames. Driver mappings and Go heap capacity can remain resident after closing; RSS is not a leak measurement on its own.74+DRM counters reported about 41 MiB VRAM and 4 MiB GTT once all three presentation buffers were active, unchanged from baseline and released at shutdown. Window dimensions, scale, fonts and driver affect these figures. These are measurements on one stack, not portable limits.
49 75
50-DRM counters reported about 41 MiB VRAM and 4 MiB GTT once all three presentation buffers were active. Window dimensions, scale, fonts and driver affect these figures. These are measurements on one stack, not portable limits.76+A separate approximately 20-second idle profile recorded about **91 KiB** allocated under `syncobj.WaitPoint` in the baseline and **no allocations attributed to that path** on the measured implementation. Other runtime and consumer instrumentation allocations remain; this is not a claim that the whole process allocates nothing at idle.
51 77
52-The largest frame-allocation sources are prepared quads, render batches, and layout painting. Release-wait polling allocated about 150 KiB over a 38-second idle interval. `NEFERGUI_DEBUG_DIR` enables readbacks and PNG work and raised observed peak RSS to about 113 MiB; leave it unset when measuring ordinary application cost.78+Prepared quads and layout painting still allocate detached outputs. Glyph rasterization and glyph-key hashing also appear in cumulative allocation profiles. `NEFERGUI_DEBUG_DIR` enables readbacks and PNG work; leave it unset when measuring ordinary application cost.
53 79
54 Use `pprof`'s `alloc_space` to find cumulative allocation churn and `inuse_space` after GC to inspect retained Go memory. Use `/proc/<pid>/smaps_rollup` for RSS/PSS and DRM fdinfo for GPU counters; heap profiles do not include driver or GPU memory.80 Use `pprof`'s `alloc_space` to find cumulative allocation churn and `inuse_space` after GC to inspect retained Go memory. Use `/proc/<pid>/smaps_rollup` for RSS/PSS and DRM fdinfo for GPU counters; heap profiles do not include driver or GPU memory.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close