关注了就能看到更多这么棒的文章哦~
By *Jake Edge*, January 7, 2026
Gemini translation
https://lwn.net/Articles/1051430/
LPC
在 12 月中旬于东京举行的 2025 Linux Plumbers Conference (Linux Plumbers 会议,简称 LPC) 上,Changwoo Min 主持了一场关于他在开发 “latency-criticality aware virtual deadline” (延迟关键性感知虚拟截止日期,简称 LAVD) 调度器过程中的经验分享。该调度器主要针对游戏负载。这次会议是 Gaming on Linux (Linux 游戏) 微型会议的一部分,这是 LPC 的一个新项目;组织者希望它能明年在布拉格回归,并以此延续。LAVD 使用了 extensible scheduler class (可扩展调度器类,简称 sched_ext),主要目标是减少游戏中的卡顿 (stuttering);它是使用 BPF 和 Rust 混合实现的。
Min 表示,他一直在 Igalia 为 SteamOS 和 Steam Deck 开发 LAVD。虽然调度器的名字有点长,但它的核心目标是让 Windows 游戏在 Linux 上运行得更好。SteamOS (以及 Linux 版 Steam 应用) 使用 Wine 和 Proton 兼容层。LAVD 的大部分调度决策代码是用 BPF 编写的,另外包含一个轻量级的、基于 Rust 的用户态部分。
关于 sched_ext 和 LAVD 的更多信息可以在去年 LPC 的一篇文章中找到。此外,今年的 sched_ext 微型会议中也有关于 LAVD 的议题,其中包括一篇关于将 LAVD 改编为 Meta 生产集群默认调度器的分享。
塑造 LAVD 的大部分经验都源于 Windows 游戏及其在 Linux 上的需求。本次会议旨在提高人们对一些缺失工具的关注,这些工具可以帮助更好地支持 Linux 游戏。他希望能够启动一些关于如何从游戏中收集更好的性能信息并进行可靠基准测试以改进调度的讨论。
游戏负载
他表示,针对游戏的调度器有两个目标:性能和能耗。游戏是独特的,因为它拥有 “一个被广泛接受的通用性能指标,即每秒帧数” (frames-per-second,简称 FPS)。FPS 测量有两种类型:平均 FPS 和 “最低 1%” 或最小 FPS。平均值衡量的是应用吞吐量,而最低 1% 衡量的是第 99 百分位延迟,这正是导致卡顿的原因。因此,目标是在提供高平均 FPS 的同时,尽可能保持最低 1% FPS 处于高位,以免让玩家感到不快——这是游戏体验中最重要的方面。

许多游戏设备 (如 Steam Deck) 采用电池供电,因此能源效率至关重要。底层硬件通常使用的处理器混合了不同类型的核心 (大核、中核和小核),具有不同的能耗特性。对于游戏,通常有多个任务,但其中一些可以在速度较慢、能效更高的核心上运行。大多数任务运行时间较短,约为 1ms,不过也有少数任务会运行 2-3ms 或更长。还有一些运行时间少于 100µs 的任务,这意味着在任务放置方面有更大的灵活性。
在他看来,调度器可以做出三个影响性能的独立决策。首先是选择哪个任务先运行;如果对延迟敏感的任务被推迟,可能会导致 “级联延迟、UI 卡顿或掉帧”,从而影响用户体验。调度器还决定任务应该运行多久;所选的时间片需要在 “运行足够长以从热缓存中获益” 与 “运行时间不过长以免独占处理器” 之间取得平衡。除此之外,调度器还为每个任务选择使用哪个 CPU;做出该决策的开销也会影响延迟和能效。快速决策可以减少调度器开销,但由于决策不佳可能会增加其他成本;如果调度器为了追求更好的决策而导致自身开销过高,也会产生问题。
理解游戏负载对于开发针对游戏优化的调度策略至关重要。刚开始时,Min 并不确定自己能否想出一个针对游戏优化的调度器,因此他着手测量负载,看看是否存在一些可以被调度器利用的共同特征。于是他开发了 VaporMark (名字取自 Steam 的蒸汽寓意),用于分析使用 perf sched record 收集的数据。“在收集了大量数据并进行了一两个小时的后处理后,它会显示一些报告。”
他展示了一个 VaporMark 报告示例 (见上图,摘自他的幻灯片)。他表示,这是一份非常有用的报告,显示了每个任务的典型运行持续时间等信息,并可以据此判断是否可以对即将到来的持续时间做出统计学上的有效预测。它提供了关于游戏任务为何以及如何被调度的洞察。
他的分析得出的关键结论或许显而易见:单个高级动作 (例如在屏幕上移动角色并根据按键事件发出声音) 需要许多任务协作完成。其中一些任务是游戏进程中的线程,而另一些则不是,因为它们位于游戏引擎、内核和设备驱动程序中;通常有一个由 20 或 30 个任务组成的链条需要协同工作。寻找具有高唤醒频率的任务并优先处理它们,正是 LAVD 调度策略的基础。
更多工具
VaporMark 对于理解游戏的高级属性很有帮助,但一旦他完成了 LAVD 的原型,就发现它不足以支持进一步的工作。他尝试了 Perfetto,它表现得更好,特别是对于 “通过微观分析发现病态调度行为”。Min 说,但这并没有解决他的分析问题,也许是因为他不是 Perfetto 专家。
VaporMark 和 Perfetto 共同面临的一个问题是,他想要分析的问题很少发生。例如,游戏在大部分时间里可能运行流畅,但每五分钟左右会出现一次延迟峰值,导致 FPS 下降。“我该如何捕捉到这一点?” 他没能找到一种方法来触发该问题以便追踪导致它的原因;相反,他采用了每十秒采样一次的暴力方法,希望能撞大运捕捉到问题的发生。“工具集中缺失了高级视图与微观视图之间的关联。”
会议组织者之一 David Vernet 指出,Perfetto 确实有方法追踪不仅仅是 CPU 时间的信息 (如内存使用情况),但它是基于采样的,因此可能无法提供 Min 所寻找的所有细节。另一位与会者询问了关于按键事件延迟追踪的问题,这对于某些类型的游戏 (尤其是职业玩家偏爱的游戏) 非常重要。这种分析可能需要比现有手段更深入地观察游戏引擎内部,但他想知道 LAVD 是否采取了任何措施来最小化这种延迟。Min 表示 LAVD 并没有专门关注这一点,但他认为其策略会自然地优先处理涉及按键处理的任务。
Vernet 指出,某些游戏 (如《文明 VI》) 具有 CPU 密集型任务,可能需要不同处理。他工作的地方 (Meta) 的一些负载也具有类似特征,他想知道 Min 是否对从初始唤醒到最后一个任务完成工作的整个任务链进行过任何分析,并以此推导出截止日期和帧率。Min 表示他还没有做过,但这是一个他想要研究的领域;例如,可能可以利用来自 GPU 的信息来帮助做出更好的 CPU 调度决策。
一位与会者提出了关于选择在哪个 CPU 上运行被唤醒任务的问题,以及根据任务之间的关系将任务移动到其他 CPU 是否有意义。Min 表示,做出这些决策是 “一件相当棘手的事情”。他一直在关注调度器的延迟管理方面,但还有其他资源需要考虑,例如缓存局部性和内存距离。需要一种工具来生成 “更全面的资源竞争视图”;也许可以使用 Perfetto 来实现,但这仍有待完成。
以及基准测试
他在开发调度器时遇到的另一个问题是,游戏的基准测试 “超级困难”。为了证明新的调度策略优于旧策略,“可靠的可重复基准测试绝对是必要的”,但游戏在这方面有所缺失,不像数据库等其他领域拥有标准基准测试。幸运的是,一些游戏 (如《赛博朋克 2077》、《孤岛惊魂》和《极限竞速:地平线》) 拥有内置基准测试,这非常有用;其他游戏 (如《反恐精英》) 允许回放录制的序列,这也有帮助。
不幸的是,目前尚不清楚这些游戏的测试结果对于其他缺乏此类能力的游戏有多少代表性;例如,一些用户抱怨 LAVD 在使用虚幻引擎 (Unreal engine) 的游戏中表现不佳。另一个问题是游戏更新频繁,因此收集到的任何数据都会很快过时;比较 2GB 游戏更新前后的结果 “没有任何意义”。内置基准测试有时也依赖于外部资源的性能,例如游戏服务器。
即使有可靠的基准测试结果,也很难将观察到的 FPS 变化与导致这些变化的调度器更改关联起来。因此,微观基准测试同样令人感兴趣,但他发现的大多数针对调度器的微观基准测试 (例如 stress-ng 和 hackbench) 都专注于给调度器加压并测量调度开销。这很有用,但改进这些数字并不总能带来更好的游戏性能。
有一些模拟特定负载的微观基准测试;一个例子是 schbench,它旨在重现生产环境 Web 服务器负载的调度特征。他说,需要类似的东西来模拟游戏负载。Vernet 询问如何构建这种基准测试;是使用现有的游戏引擎,还是以某种方式引入 AAA 游戏 (大作)?Min 表示他不确定该如何着手;一个问题是,最重要的游戏和引擎会根据市场份额而变化。
一个基准测试套件可能会有所帮助。他指出,CloudSuite 提供了一组打包成基准测试套件的代表性云应用。或许可以将一组游戏转化为用于调度器测试的 “gamesuite”;它将补充一些运行时间短、反馈快的微观基准测试。内置基准测试通常只给出平均 FPS,“这很有用但并不理想”;它们缺乏真正需要的最低 1% 和最小 FPS 信息。
另一位会议组织者 André Almeida 建议使用 MangoHud 来收集更多 FPS 数据;Min 表示赞同,但指出 MangoHud 需要仔细配置。在 “批量” 模式下使用时,MangoHud 的日志记录可能会干扰游戏,导致卡顿。一个 gamesuite 潜在地可以包含推荐的 MangoHud 配置以及一组用于运行的代表性游戏。
锁
他的关注点很大程度上在于 Windows 游戏,这些游戏使用底层操作系统的同步原语。对于 Linux,这些原语是由 Wine 层使用 futex 模拟的,但仍有工作要做。Windows 游戏中的大多数任务都是短时间运行且计算密集的,因此避免因锁竞争导致任务无法快速完成的情况至关重要。这意味着要避免持锁者抢占 (lock-holder preemption,或称优先级反转 (priority inversion)) —— 即当锁持有者被调度出 CPU,而新调度的任务需要该锁从而无法继续执行的情况。Min 说,Almeida 一直在做的重构 futex API 的工作也将有助于解决优先级反转问题。
在内核中,代理执行 (proxy execution) 在他看来是正确的方法,但它对用户态 futex 没有帮助。LAVD 尝试追踪任务和 futex,以决定何时为任务提供更长的时间片,以便让它有时间释放锁;然而,这并不是该问题的完整解决方案。与此同时,Wine 正在转向使用 ntsync 驱动程序,该驱动旨在更好地支持 Windows NT 同步原语。他认为,最终 ntsync 和代理执行应该有助于缓解这类问题。
Almeida 指出,在快速路径上,内核并不知道哪个任务持有 futex,因此在做出调度决策时没有这些信息。他听说 FreeBSD 程序总是会告知内核 futex 的所有者,以便内核避免优先级反转。然而,系统调用的成本很高,因此强制要求这样做可能会有问题。会议还讨论了其他 Windows 锁类型以及 Linux 对它们的支持情况;结论似乎是这些锁是构建在 futex 接口之上的,因此修复 futex 应该能解决 Windows 锁的大部分问题——至少对于游戏来说是这样。
一位与会者询问 LAVD 是否在 Steam Deck 上对所有游戏使用相同的调度器。Min 表示 LAVD 是一个单一的调度器,尽管可以通过命令行参数调整一些旋钮。该与会者想知道考虑类似 “配置文件引导的调度” (profile-guided scheduling) 是否有意义,即使用游戏的配置文件来设置调度器的参数。在大多数情况下,这些配置文件只需要针对特定引擎,因为大多数游戏使用现成的或内部的引擎,这些引擎决定了它们的性能特征。Min 同意这听起来是一个有趣的方法,但他尚未对此进行探索。
在后续问题中,他询问了关于可重复的游戏测试;如果没有涉及网络服务器,除了随机数的生成外,还有哪些因素需要约束以重现游戏运行?Min 表示随机数是游戏确定性的主要障碍,但也有其他因素在起作用。如果游戏运行太快,可能会导致 CPU 过热并被降频;这显然也会改变其特征。
会议最后讨论了使用 WF_SYNC (或 WF_CURRENT_CPU) 标志来标识那些在唤醒后会立即返回睡眠状态的任务。这可以帮助内核做出更好的调度决策,Android 系统就是这样使用的。感兴趣的读者可以参考会议的 YouTube 视频以了解更多细节。
[ 感谢我们的差旅赞助商 Linux Foundation 为我前往东京参加 Linux Plumbers Conference 提供的资助。 ]
LWN 评论概述
评论区围绕游戏负载的特殊性及内核工具的优化展开了深入讨论。有用户提出,FreeBSD 让用户态告知内核锁持有者的做法虽然增加了系统调用开销,但或许可以通过每个线程的共享内存来实现,从而让调度器在不进入内核的情况下获益,避免优先级反转。针对资源竞争分析,有评论推荐了 Josef Bacik 开发的 systing 工具,认为它能提供更全面的视角。
此外,讨论还涉及了外部调度器(如 sched_ext)与内置调度器之间的权衡:一方面,外部调度器允许用户快速适配游戏引擎的频繁更新;另一方面,开发者担心这会削弱内核核心调度器的反馈回路。最后,针对难以捕捉的瞬间延迟峰值,评论者引用了“最大值即信号”的观点,建议在出现严重延迟时让内核像处理 panic 一样转储尽可能多的调试信息,而不是将其视为噪声忽略。
全文完LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~




402

被折叠的 条评论
为什么被折叠?



