ccdbind
Background daemon for automatic CPU affinity management
ccdbind
ccdbind is a systemd user daemon that automatically detects game processes and manages CPU affinity.
How It Works
Topology Detection
On startup, ccdbind reads /sys/devices/system/cpu/cpu*/cache/index3/shared_cpu_list to discover L3 cache groups (CCDs).
Process Scanning
Every 2 seconds (configurable), it scans /proc for processes with Steam/Proton environment variables.
Pinning
When a game is detected:
- Pin
app.sliceandbackground.sliceto OS CPUs - Create a transient scope under
game.slice - Move game PIDs to the scope
- Pin the scope to GAME CPUs
Restoration
When no games are running, restore original CPU settings.
Usage
Start the daemon
systemctl --user start ccdbind.serviceEnable at login
systemctl --user enable ccdbind.serviceCheck status
ccdbind statusView logs
journalctl --user -u ccdbind.service -fCLI Flags
| Flag | Description |
|---|---|
--config <path> | Config file path |
--interval <dur> | Poll interval (e.g., 1s, 500ms) |
--print-topology | Print detected CPU groups and exit |
--dry-run | Log actions without executing |
--dump-state | Print persisted state JSON and exit |
Examples
# Print detected topology
ccdbind --print-topology
# Test configuration without making changes
ccdbind --dry-run
# Use custom config
ccdbind --config /path/to/config.toml
# Override poll interval
ccdbind --interval 500msStatus Command
The status subcommand shows current state:
ccdbind statusOutput:
Topology:
OS CPUs: 0-5,12-17
Game CPUs: 6-11,18-23
Status: GAME ACTIVE
Pinned Slices:
app.slice → 0-5,12-17
background.slice → 0-5,12-17
Active Games:
game-765432.scope (Cyberpunk2077.exe)
PIDs: 12345, 12346, 12347
CPUs: 6-11,18-23Status Flags
| Flag | Description |
|---|---|
--json | Output as JSON |
--filter=all | Show all info including inactive |
# JSON output for scripting
ccdbind status --json
# Show everything
ccdbind status --filter=allSystemd Integration
Service Unit
The service unit (~/.config/systemd/user/ccdbind.service):
[Unit]
Description=CCD CPU Binding Daemon
Documentation=https://github.com/youruser/quicksetd
[Service]
Type=simple
ExecStart=%h/.local/bin/ccdbind
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.targetGame Slice
Games are placed in game.slice (~/.config/systemd/user/game.slice):
[Unit]
Description=Slice for game processes
Before=slices.target
[Slice]D-Bus API
ccdbind uses the systemd user manager D-Bus API:
StartTransientUnit- Create game scopesAttachProcessesToUnit- Move processes to scopesSetUnitProperties- SetAllowedCPUson slices/scopes
ccdbind operates entirely in user space. No root access required.
Game Detection
Primary: Environment Variables
ccdbind scans /proc/<pid>/environ for Steam-related variables:
SteamAppIdSteamGameIdSTEAM_COMPAT_APP_ID
These are automatically set by Steam for all games.
Secondary: Executable Allowlist
For non-Steam games, add executables to exe_allowlist:
exe_allowlist = ["lutris-wrapper", "heroic-game"]Ignored Processes
Some processes have Steam variables but aren't games:
ignore_exe = [
"steam",
"steamwebhelper",
"pressure-vessel",
"reaper",
]State Management
ccdbind persists state to handle crashes and restarts:
{
"os_pinned": true,
"original_cpus": {
"app.slice": "0-23",
"background.slice": "0-23"
},
"game_scopes": ["game-765432.scope"]
}On startup:
- If state shows games were active, verify and clean up stale scopes
- Restore original CPU settings if no games are running
Troubleshooting
Daemon won't start
# Check logs
journalctl --user -u ccdbind.service
# Test config
ccdbind --config ~/.config/ccdbind/config.toml --dry-runGames not detected
# Check if Steam env vars are set
cat /proc/<game_pid>/environ | tr '\0' '\n' | grep -i steam
# Add to allowlist if needed
exe_allowlist = ["game-executable"]Pinning not working
# Check current CPU assignments
systemctl --user show app.slice | grep AllowedCPUs
# Verify topology detection
ccdbind --print-topologyPerformance issues
Reduce poll interval impact:
interval = "5s" # Less frequent scanning