背景:显存用完了,游戏真的没救吗?
游戏显存(VRAM)耗尽时,传统经验是”完了”——帧率暴跌到没法玩,游戏随机崩溃。但 Linux 内核开发者 pixelcluster 认为这不一定是宿命。他今年初开始改进内核的显存管理,相关补丁在邮件列表里漂了几个月后,终于被合入上游,随 Linux 7.3 发布。这篇深度长文详细拆解了”爆显存”背后的机制与解决方案。
为什么爆显存会这么卡?
显存超卖(overcommit)本身不是新东西:驱动允许应用申请超过物理显存的内存,超出的部分被驱逐到 CPU 内存,GPU 再通过 PCIe 总线取回。物理限制很残酷:PCIe 4.0 x16 的带宽约 32GiB/s,按 30 FPS 算,每帧最多只能从被驱逐的内存里取回约 1GiB 数据——这是硬上限。但缓存能救场:命中缓存的访问延迟与内存位置无关;不过 CPU 内存访问不走 Infinity Cache,延迟约为缓存命中的 7.3 倍、VRAM 直读的 4.6 倍。所以”缓存友好 + 低频访问”的内存被驱逐,影响其实有限。
真正的元凶:内核锁死循环与扫描输出
稳定性问题比性能更致命。作者遇到了 “Not enough memory for command submission” 的报错——明明所有分配都成功了,提交命令时却返回 -ENOMEM。根因是内核 TTM(共享 GPU 内存管理层)没有使用 drm_exec 辅助库:两个提交互相需要对方锁住的内存时形成 ABBA 死锁,标准做法是”wound-abort-retry”重试机制,但 TTM 里直接放弃驱逐导致提交失败。作者把 2024 年搁浅的补丁集捡起来 rebase,花了一周时间修掉剩余 bug——代价是游戏随机卡死三分钟。
最恶劣的场景是显示扫描输出(scanout):显示器数据必须物理连续地驻留 VRAM,而 LRU 驱逐算法完全不考虑物理连续性。实测为了给 32MiB 的扫描图像腾地方,内核最多驱逐了整整 4GiB 的显存,光搬运就耗时约 130ms。
Linux 7.3 的解法:硬/软节流启发式
最终合入的是一套节流启发式:检测到应用内存被驱逐后,先进入”硬节流”(数毫秒内完全不抢回内存),再进入”软节流”(最多几秒,可以抢回自己的内存但不驱逐别人的),稳定后解除限制。实测《夺宝奇兵:大圆环》在 8GiB 显卡上请求 9GiB 显存(超卖 1GiB),平均帧时间 19.6ms,完全可玩;超卖 2GiB 时平均 29.8ms,帧时间波动明显但不再崩溃。未来的方向是支持 VK_EXT_pageable_device_local_memory 优先级提示——vkd3d-proton 已把 D3D12 的显存驻留 API 翻译成对应优先级,最好情况下性能可提升约 30%。
社区反响
HN 讨论区(265 条评论)一片好评:”写得太清楚了,信息量十足””很高兴内核有这些热情的人”。有玩家表示终于搞懂了以前那些绕来绕去的 tweak 到底在做什么;也有评论者深入讨论扫描输出的驱逐策略,以及能否通过搬移物理内存 + 更新页表来腾出连续空间。