Skip to content

Latest commit

 

History

History
129 lines (104 loc) · 6.14 KB

performance.md

File metadata and controls

129 lines (104 loc) · 6.14 KB

Performance

nbshell keeps the bar, wallpaper, notification popups, OSD, runtime services, and IPC handlers resident. Larger surfaces that are not useful in the background—such as display settings, the process list, dashboard, notes, and the wallpaper picker and unified Library—are created only while open and destroyed after closing. Launcher window, clipboard, and calculator providers reuse state already held by the shell. File search starts a capped fd process only for an explicit @ query. System reports, demo capture, and video exports are CLI jobs and leave no resident helper behind.

Shell motion is event-driven. It animates compositor-friendly properties such as opacity, scale, position, and list offsets only while a visible state is changing. There is no animation daemon, continuous canvas, or wallpaper thumbnail conversion job. Settings → Appearance → Motion offers reduced, standard, and expressive; reduced motion resolves all shared durations to zero.

The systemd service also configures jemalloc for Quickshell:

MALLOC_CONF=thp:never,narenas:4,dirty_decay_ms:3000

Quickshell on Arch links against jemalloc. Limiting arenas and preventing small allocations from using transparent huge pages substantially reduces idle private memory. A user can override MALLOC_CONF in a later systemd user drop-in if a different allocator policy is required.

The installed service sets NBSHELL_DISABLE_HOT_RELOAD=1, so a production session does not keep file watches for the deployed QML tree. A manual nbshell start leaves hot reload enabled for development.

Resident services prefer compositor, D-Bus, and file notifications over child process polling. Optional phone and tailnet services are instantiated by their configured widget or an explicit IPC call rather than every session. Display hot-plugs refresh immediately from Quickshell's screen signal; a five-minute fallback covers output-property changes that do not change the screen list. Media positions and Cava samples run only while a loaded UI consumes them.

Measuring

Use proportional set size rather than RSS when comparing shell revisions. Qt and graphics libraries map many shared pages that RSS counts in every process:

pid=$(systemctl --user show nbshell.service -p MainPID --value)
grep -E '^(Rss|Pss|Pss_Anon|AnonHugePages):' /proc/$pid/smaps_rollup

Measure after the shell has been idle for several seconds. Open and close each surface once, wait again, and repeat the measurement to distinguish the clean startup baseline from caches retained after normal use. Performance numbers are diagnostics rather than release-test thresholds because GPU drivers, screen count, plugins, and enabled modules materially change them.

Panel loading profile

The isolated Wayland fixture can compare synchronous panel creation with an asynchronous experiment. It requires Umbriel, Quickshell, Bubblewrap, dbus-run-session, grim, Python Pillow, and an accessible DRM render node. Run the variants sequentially from the same source tree, using fresh output paths:

python3 tests/wayland-lifecycle.py --compositor /usr/local/bin/umbriel \
  --render-node /dev/dri/renderD128 --output /tmp/nbshell-panel-sync \
  --cycles 20 --settle-seconds 0 --panel-profile
python3 tests/wayland-lifecycle.py --compositor /usr/local/bin/umbriel \
  --render-node /dev/dri/renderD128 --output /tmp/nbshell-panel-async \
  --cycles 20 --settle-seconds 0 --panel-profile --panel-async

The experiment changes only the private shell copy. Both outputs contain a panel-profile.json, screenshots, and logs. Compare baselineQmlSha256 before comparing results. The fixture opens Settings, Modules, Agents, Dashboard, and the Plugin Manager, destroys each panel after closing, and requires exactly one first-swap event per new panel instance.

The three measurements answer different questions:

  • qtFirstSwapMs: in-process open request to that panel's first Qt frameSwapped callback. This may still be an initial transparent frame; it does not measure physical display scanout.
  • visibleReadbackUpperBoundMs: request through screenshot capture, PNG decoding, and central-region pixel comparison. It confirms visible output but includes substantial IPC/readback overhead. The fixed pixel threshold is intended for the default 800×600 fixture, not cross-resolution comparisons.
  • maxHeartbeatGapMs: largest Qt timer gap during the request/readback window. It includes render scheduling and unrelated shell activity, so it does not attribute blocked CPU time to a particular component.

On 2026-09-08, 20 openings per panel and variant on the same source produced these p95 values in the private software-rendered 800×600 fixture (milliseconds):

Panel First Qt swap, sync / async Heartbeat gap, sync / async
Settings 10 / 11 20 / 18
Modules 15 / 16 21 / 23
Agents 15 / 15 17 / 18
Dashboard 34 / 30 29 / 25
Plugin Manager 13 / 12 17 / 17

Keep synchronous loading: the experiment did not establish a consistent improvement. Screenshot upper bounds were lower for several async panels, but include measurement overhead and sequential-run host-load differences. These small samples are diagnostic evidence, not a general performance promise or a release threshold. Raw cycles include the first opening, allowing it to be inspected separately from subsequent openings.

Runaway application protection

nbshell can optionally enable systemd-oomd for graphical applications:

nbshell memory-guard setup
nbshell memory-guard status
nbshell memory-guard remove

The shell and its Umbriel resume guard run in session.slice; applications remain in app.slice. When enabled, systemd-oomd may reclaim an application cgroup after app.slice stays above 60% memory pressure for 20 seconds, with swap exhaustion as a second backstop. This protects the interactive desktop without adding a resident nbshell process. The setting is opt-in because an OOM decision can close an application that has not saved its work.

remove deletes only nbshell's app-slice policy. It deliberately leaves the shared systemd-oomd daemon enabled in case another desktop component uses it.