NeferWL uses about 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change.
- Only changed areas are redrawn. Windows nobody sees get one frame callback per second.
- Windows are composed as instanced draws, so a frame makes a few Vulkan calls whatever the number of windows.
- Fullscreen windows go straight to the display (direct scanout), with tearing for games. See
render.direct-scanout and render.tearing in Configuration.
- A full frame at 2560×1600 takes about 0.16 ms on an RX 9070 XT and 3 ms on a Radeon 680M. Measure yours with
go test ./internal/adapters/vulkan -bench ComposeFullTarget (NEFERWL_VK_DEVICE picks the GPU).
- NeferWL runs with
GOGC=100 and a 256 MiB soft memory limit. GOGC and GOMEMLIMIT from the environment override them.
VRR
With a fullscreen game under VRR, each game frame is committed at least render.vrr-flip-gap (default 1 ms) after the previous flip. On amdgpu, committing earlier can hold the panel at its slowest refresh rate.
Real-time priority
Output and input threads ask for real-time scheduling, which needs CAP_SYS_NICE. The Arch and Debian packages set it. For a local build:
sudo setcap cap_sys_nice+ep bin/neferwl
performance.realtime = off turns it off. With the capability set, the loader ignores LD_PRELOAD and LD_LIBRARY_PATH, and debuggers need root to attach.
Profiling
Run these from another TTY or over SSH:
# CPU, Go and C frames
perf record -g -p (pgrep -f bin/neferwl) -- sleep 8
perf report --children --percent-limit 1
# Allocations
neferwl --backend=drm --pprof=localhost:6060
go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/heap
# One line per GC cycle
GODEBUG=gctrace=1 neferwl --backend=drm
make tty serves pprof on localhost:6060 by default (make tty PPROF= turns it off).
Logs are in ~/.local/state/neferwl/runs/<backend>/, with latest.log pointing at the newest run. --debug=all enables every category except the noisy drm-flip, input-motion, input-keys, wayland-damage (one client damage entry per client commit) and render-damage (one frame damage entry per rendered frame); name them to turn them on.
Frame stats
Every 10 s, each output logs a stats entry:
| Field |
Meaning |
missed_vblanks |
Frames that flipped more than 1.5 refresh periods late (not counted under VRR) |
max_flip_interval_ms |
Longest gap between two flips |
max_commit_delay_ms |
Longest wait from "frame due" to commit |
late_fences |
Missed vblanks where the GPU finished too late (needs --debug=drm-flip) |
redrawn_pixels |
Pixels drawn |
If outputs report flips faster than the display loop reads them, wayland merges them and logs a warning with merged at most every 10 s.
Missed vblanks with late_fences mean the GPU was late. Without late_fences but with a large max_commit_delay_ms, the CPU was late.
source
1# Performance
2
3NeferWL uses about 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change.
4
5- Only changed areas are redrawn. Windows nobody sees get one frame callback per second.
6- Windows are composed as instanced draws, so a frame makes a few Vulkan calls whatever the number of windows.
7- Fullscreen windows go straight to the display (direct scanout), with tearing for games. See `render.direct-scanout` and `render.tearing` in [Configuration](config.md#keys).
8- A full frame at 2560×1600 takes about 0.16 ms on an RX 9070 XT and 3 ms on a Radeon 680M. Measure yours with `go test ./internal/adapters/vulkan -bench ComposeFullTarget` (`NEFERWL_VK_DEVICE` picks the GPU).
9- NeferWL runs with `GOGC=100` and a 256 MiB soft memory limit. `GOGC` and `GOMEMLIMIT` from the environment override them.
10
11## VRR
12
13With a fullscreen game under VRR, each game frame is committed at least `render.vrr-flip-gap` (default 1 ms) after the previous flip. On amdgpu, committing earlier can hold the panel at its slowest refresh rate.
14
15## Real-time priority
16
17Output and input threads ask for real-time scheduling, which needs `CAP_SYS_NICE`. The Arch and Debian packages set it. For a local build:
18
19```sh
20sudo setcap cap_sys_nice+ep bin/neferwl
21```
22
23`performance.realtime = off` turns it off. With the capability set, the loader ignores `LD_PRELOAD` and `LD_LIBRARY_PATH`, and debuggers need root to attach.
24
25## Profiling
26
27Run these from another TTY or over SSH:
28
29```sh
30# CPU, Go and C frames
31perf record -g -p (pgrep -f bin/neferwl) -- sleep 8
32perf report --children --percent-limit 1
33
34# Allocations
35neferwl --backend=drm --pprof=localhost:6060
36go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/heap
37
38# One line per GC cycle
39GODEBUG=gctrace=1 neferwl --backend=drm
40```
41
42`make tty` serves pprof on `localhost:6060` by default (`make tty PPROF=` turns it off).
43
44Logs are in `~/.local/state/neferwl/runs/<backend>/`, with `latest.log` pointing at the newest run. `--debug=all` enables every category except the noisy `drm-flip`, `input-motion`, `input-keys`, `wayland-damage` (one `client damage` entry per client commit) and `render-damage` (one `frame damage` entry per rendered frame); name them to turn them on.
45
46## Frame stats
47
48Every 10 s, each output logs a `stats` entry:
49
50| Field | Meaning |
51|---|---|
52| `missed_vblanks` | Frames that flipped more than 1.5 refresh periods late (not counted under VRR) |
53| `max_flip_interval_ms` | Longest gap between two flips |
54| `max_commit_delay_ms` | Longest wait from "frame due" to commit |
55| `late_fences` | Missed vblanks where the GPU finished too late (needs `--debug=drm-flip`) |
56| `redrawn_pixels` | Pixels drawn |
57
58If outputs report flips faster than the display loop reads them, wayland merges them and logs a warning with `merged` at most every 10 s.
59
60Missed vblanks with `late_fences` mean the GPU was late. Without `late_fences` but with a large `max_commit_delay_ms`, the CPU was late.