远程用完电脑,GPU 和虚拟屏却没闲下来
远程用完电脑,GPU 和虚拟屏却没闲下来
远程会话结束后,桌面看起来空闲,独显却可能仍被后台应用唤醒;Mac 上为远程使用而开的虚拟带鱼屏也可能继续保留。它们分别发生在 Windows 和 macOS,不能用一个“远程结束”开关解决。
本篇把两个记录放进同一条收尾思路:先确定谁在使用资源,再确认是否还有会话,最后才考虑改设置或关闭显示器。Windows 的 LegionZone 退出后活动回落是历史观察;RustDesk 的单连接脚本需要修订,本文不把它包装成已验证的多会话自动化。
GPU 的 0% 为什么不代表完全没有活动
任务管理器看不同引擎,NVIDIA SMI 的设备利用率、功耗和 P-state 又是另一组指标。3D、Copy、Video Encode 等引擎不应直接相加成设备总利用率;短暂桌面渲染也可能让某次 nvidia-smi 的 0% 与另一个窗口里的引擎活动同时成立。任务管理器 GPU 统计机制
首先固定外接屏、供电方式、MUX/混合显卡模式、应用窗口状态和监控轮询。记录同一时间范围的引擎与设备读数,然后观察退出候选进程后的变化。不要在这一轮同时切显卡模式、改驱动和关显示器,那样无法区分哪项起作用。
历史记录的三组状态必须分别保留:
| 状态 | 原始记录 | 结论边界 |
|---|---|---|
| LegionZone 运行 | 30 次循环,26 条超阈值引擎记录;旧汇总 Mean 9.1、Peak 20.7;11.87 W、P5 | 有候选活动来源;不是独显设备平均利用率 |
| 完全退出后 | NONE above 1%;7.61 W、P8 | 在该窗口活动回落;不是所有引擎严格为零 |
| 写入偏好并重启后 | NONE above 1%;7.61 W、P5 | 未看到超阈值活动;没有证明成功改用核显 |
26 是引擎记录数,同轮同 PID 可能命中多个引擎,不能写成 26/30 的时间占比。9.1 是筛选后记录的均值,不包含低值。旧循环包含每次计数器调用与 sleep,不能凭 30×2 推出严格 60 秒;原工具总耗时约 95 秒,新的采集应使用时间戳。
保留原始引擎实例,再做汇总
下面是只读 PowerShell 参考采集,输出到新的 CSV 文件,未补做 Windows 实机运行验收。英文计数器路径在本地化 Windows 上可能不同,先用 Get-Counter -ListSet 查看名称。
$gpuSamples = Get-Counter -Counter '\GPU Engine(*)\Utilization Percentage' `
-SampleInterval 2 -MaxSamples 30 -ErrorAction Stop
$gpuRows = foreach ($sample in $gpuSamples) {
foreach ($counter in $sample.CounterSamples) {
[pscustomobject]@{
Timestamp = $sample.Timestamp.ToString('o')
Instance = $counter.InstanceName
Status = $counter.Status
Value = $counter.CookedValue
}
}
}
$gpuCsv = Join-Path $env:TEMP ('gpu-engines-' + [guid]::NewGuid().ToString('N') + '.csv')
$gpuRows | Export-Csv -LiteralPath $gpuCsv -NoTypeInformation -Encoding UTF8
$gpuCsv实例里通常含 PID、适配器 LUID、物理索引和引擎类型。分析时保留这些维度,匹配 PID 对应的进程路径与开始时间;PID 会复用,不能用结束后看到的同 PID 自动归因过去样本。无效状态、未解析实例与采样失败单独统计,不填成零。CSV 可能含用户路径,分享前脱敏。
同步设备读数:
nvidia-smi --query-gpu=timestamp,name,pstate,power.draw,utilization.gpu --format=csv -l 2不支持的字段保留 N/A,不把空进程列表当作没有图形负载。NVIDIA SMI 字段说明
进程归因与核显迁移是两项验收
退出 LegionZone 后活动下降,支持它是值得继续处理的来源。要增强归因,还应在相同条件下恢复应用,确认负载重新出现,做 A/B/A 重复。历史恢复了 6 个进程,但没有充分记录原负载重现。
原操作写入 GpuPreference=2; 并把它叫作核显偏好,这个解释不能保留。正式 DXGI 枚举中的 2 是高性能偏好,不是最低功耗;该 API 枚举也不是注册表全部格式的保证。本文不提供盲写数字的修复命令。DXGI_GPU_PREFERENCE
在实际桌面用户的“设置 → 系统 → 显示 → 图形”中选择当前可执行文件,记录原选项,选择节能并核对具名 GPU,完整退出重开,再看任务管理器的引擎对应哪张设备。HKCU 依赖用户;维护账号写自己的键不代表改变桌面账号。若独显直连使核显不可用,节能标签也不能保证迁移。最后测试托盘、传感器和设备控制功能,不能提前承诺零损失。Windows 图形偏好流程
回滚只恢复目标应用的原选项。相同功耗而不同 P-state 是不同状态,不能把退出后的 P8 移到设置后的 P5;两个功耗点也不能计算长期节电量。
RustDesk 日志比端口更接近会话,但不是权威 API
Mac 端记录想实现“断开远程 15 分钟后,关闭 43:18 虚拟屏”。现场 RustDesk 日志包含连接打开、关闭和 loop exited 的 ID。relay 模式下,一个出站 relay 连接不能等同一个活跃远程桌面,端口 LISTEN 更不能说明正在有人使用。
tail -n 100 "$HOME/Library/Logs/RustDesk/RustDesk_rCURRENT.log"可以先观察当前版本真实日志格式,但本文不采用“RustDesk 所有版本都没有 Session API”这个绝对结论,也不推测未知 IPC 协议。日志中的 opened 在具体阶段代表什么,需要与一次完整的实际连入/退出对照;它可能描述连接建立而非已授权的桌面控制。
原脚本取最后一条 opened,再找相同 ID 的 closed。这样会漏掉更早但仍未关闭的连接;还没有处理日志轮转、进程重启、ID 重用与日志丢失。代码中的“用户手动重开虚拟屏会清标志”也没有对应实现:只有 active 分支清 flag,不能把注释当成行为。
自动关闭前需要一个保守的状态机
不要直接把原脚本作为后台任务部署。先让判断器只打印状态,验证以下模型,再连接显示器控制:
读取从已知边界开始的连接事件
按进程运行周期与 connection ID 维护全部未关闭连接
存在未关闭连接 → ACTIVE
轮转/重启/缺日志/无法解析/时间倒退 → UNKNOWN
连续观测可信且全部会话关闭 → IDLE(last_all_closed)
IDLE 持续 15 分钟 → 仅产生关闭建议
UNKNOWN 或 ACTIVE → 不关闭显示器“已知边界”很关键:只读最后 100 行无法证明更早的连接已退出。首次部署、没有连接记录、日志损坏都不能默认空闲。应留出完整连接周期建立可信基线;重启后重新确认,而不是把以前的空闲计时继续用下去。
| 输入场景 | 预期判断 |
|---|---|
| A 打开,B 打开,B 关闭 | ACTIVE,因为 A 仍在 |
| A、B 都关闭,可信观测满 15 分钟 | 可提出关闭建议 |
| 日志不存在或不可读 | UNKNOWN |
| 只看到 closed,没有相应 opened | UNKNOWN,不能假定历史完整 |
| RustDesk 重启或日志轮转无法接续 | UNKNOWN,重新建立基线 |
| 用户手动重新开启虚拟屏 | 需要明确的临时豁免或新的空闲周期 |
这些是修订后的验收要求,未在本机执行新的自动关闭实验。若不能证明没有使用者,保留虚拟屏比误关更容易恢复。
关闭显示器也要按对象与版本验收
原记录修改 connected@VirtualScreen:160 并重启 BetterDisplay。这是当时的对象标识和内部偏好,不是跨机器 API;重启整个应用可能影响其他显示器。BetterDisplay 提供集成功能与 CLI,应先通过当前版本帮助列举目标,再做单显示器试验。BetterDisplay 集成说明
先记录真实显示器、虚拟显示器唯一标识、开关状态与原配置。判断器输出建议后,在有人可恢复的窗口验证目标确实关闭、真实屏未受影响;失败不写“已处理”标记。人工重开应覆盖自动化意图,而不是两分钟后又被关掉。
最终可以把采集、会话判定、动作三个部分独立部署与回滚:停止动作部分不会丢采集记录,停定时任务后可手动恢复显示器,Windows 图形设置单独还原。串流编码故障见Moonlight/Sunshine 篇,不要用这篇的后台归因结论解释所有串流超时。
本文日期采用主 KB 首次 Git 提交日期 2026-09-15(UTC+8),提交 a13b11a。合并来源保留在元数据中;历史操作时间与入库时间分别注明。修订后的配置示例未在生产设备执行。
