On this page
OptimizationLow Latency

Performance & Latency Tuning

System-level optimizations for Linux kernels, GPU governors, CPU pinning, and network transport.

7 min readUpdated August 2026

Ready-made profiles#

Copy one profile into ~/.config/sunshine/sunshine.conf, then tune Moonlight bitrate to the link. Measure with the client overlay before changing more keys.

Wired NVIDIA, 1080p120 or 1440p120.

ini
busy_poll_us = 50
rate_cap_pct = 90
enet_4mib_buffer = true
dscp_qos = true
cpu_pinning = true
gpu_governor = true
latency_mode = aggressive
pipewire_latency_ms = 4
nvenc_tuning_preset = 0

CPU Frequency Governor & Pinning#

For 120 FPS + 4K streaming, verify your CPU governor is set to performance and enable SolarFlare CPU pinning:

cpu_pinning = true elevates the capture worker to SCHED_RR real-time policy and pins the thread to a non-IRQ core, bypassing general OS context-switch overhead.

bash
# Check current scaling governor
cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Set all cores to performance
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

GPU Clocks & Power Governors#

  • AMD GPUs: Setting gpu_governor = true in sunshine.conf forces power_dpm_force_performance_level to performance during active streaming.
  • NVIDIA GPUs: Prevent GPU down-clocking during video capture using clock locking (nvidia-smi -lgc <boost_clock>).
bash
# Query maximum graphics boost clock
nvidia-smi --query-gpu=clocks.max.graphics --format=csv,noheader

# Lock GPU clock to boost frequency (e.g. 2100 MHz)
sudo nvidia-smi -lgc 2100,2100

Network Buffers & Low-Latency Polling#

Wi-Fi and high-bitrate LAN links benefit from expanded socket buffers and busy-polling:

enet_4mib_buffer = true
Networkdefault: true

Expands socket buffers so 50+ Mbps bursts do not get dropped in kernel send queues.

busy_poll_us = 50
Networkdefault: 50

Enables kernel SO_BUSY_POLL for instant UDP wakeup without burning full CPU cores.

dscp_qos = true
Networkdefault: true

Marks streaming packets with DSCP CS3 so QoS-enabled routers prioritize stream traffic.

Recommended Kernel sysctls#

Add the following tweaks to /etc/sysctl.d/99-streaming.conf:

ini
# Increase socket max buffer sizes for 4K streaming
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 4194304
net.core.wmem_default = 4194304

# Enable BBR congestion control
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

Latency deep-dive: measure first#

Read the Moonlight overlay (decode + network + render) next to host GET /api/stream/latency (capture_ms, encode_ms, network_total_ms, network_queue_dwell_ms, rtt_ms) and GET /api/stream/telemetry (CPU/GPU/RAM pressure). If capture_ms is high, suspect the compositor or CPU pinning; if encode_ms is high, drop a preset level or resolution; if network/rtt dominates, run iperf3 UDP at the Moonlight bitrate and keep loss under 5% with jitter under 1 ms before touching knobs.

bash
iperf3 -s
iperf3 -c <host> -u -b 80M -t 10
ping -c 100 <host> | tail -3
curl -sk -H "Authorization: Bearer $TOK" https://localhost:47990/api/stream/latency | jq .capture_ms,.encode_ms,.rtt_ms

Vendor & link profiles#

Start from the closest profile, then A/B one knob at a time and re-measure:

ini
encoder = vaapi
capture = kms
latency_mode = safe
pipewire_latency_ms = 8

HDR streaming chain#

HDR needs the whole chain: KMS capture, an HDR compositor (Plasma 6, Gamescope), a 10-bit encoder (hevc_mode or av1_mode 3+), HDR enabled in host OS and Moonlight client, and an EDID emulator or HDR display on the host. Washed-out output means something fell back to SDR — check the colorspace lines in the log and the IsHdrSupported negotiation.

Symptom to knob table#

SymptomCheckKnob
KMS black + Probably not permittedgetcap, /dev/dri permssetcap; video group
vainfo failslibva driver presentInstall driver or encoder = software
No monitorxrandr --listmonitorsVIRTUAL1 or headless_mode
No sinkpactl/wpctl sinksaudio_sink = name
Loss over 5%iperf3 UDPrate_cap_pct down; adaptive on
401 from APICredentialssunshine --creds reset
403 from APIScope / origin / CSRFToken scope; origin_web_ui_allowed; X-CSRF-Token
Update stuck waiting_idleGET /api/sessionsEnd stream or cancel