Linux延迟测量与合成器调优
摘要
一项详细调查,使用基于Teensy的LDAT工具测量游戏中的Linux延迟,在KDE Wayland下的Nvidia GPU上使用各种设置测量点击到光子延迟,并与Windows进行比较。
<p><a href="https://lobste.rs/s/fg2oe9/linux_latency_measurements_compositor">评论</a></p>
查看缓存全文
缓存时间: 2026/06/11 13:38
# Linux 延迟测量与合成器调优 | farnoy.dev
来源:https://farnoy.dev/posts/linux-latency
自从从 Windows 迁移回来后,我一直对 Linux 上的游戏延迟感到焦虑。环境或设置的细微变化,可能会突然让鼠标变得非常飘忽。社区已经有很多讨论(https://old.reddit.com/r/linux_gaming/comments/1mg8vtl/low_latency_gaming_guide/)(https://old.reddit.com/r/linux_gaming/comments/1sq1l3a/competitive_gaming_on_linux_best_ways_to_reduce/)(https://old.reddit.com/r/linux_gaming/comments/1rmi5i3/genuinely_losing_my_mind_over_input_latency/)(https://old.reddit.com/r/linux_gaming/comments/1r8wop3/is_wayland_really_low_latency_afterall/)(https://old.reddit.com/r/linux_gaming/comments/16okrby/input_latency_on_linux_compared_to_windows/),我绝不是唯一有这种感觉的人。
为了调查,我用一块小小的 Teensy 微控制器来测量点击到显示(click-to-photon)的延迟。它充当 USB HID 鼠标,并配合一个贴在屏幕上的光传感器使用。我给 Teensy 刷入了现有的开源 LDAT(https://github.com/S4N-T0S/Open-Source-LDAT)固件,只做了轻微修改。这个设置可以无人值守地自动记录数百个样本到 CSV 文件中。
测量在两台电脑上进行:一台台式机和一台笔记本。它们都搭载了 Ada 代 RTX 显卡和 Zen 4 处理器。两台设备基本都使用了相同的 NixOS 配置,并且各自都安装了最新的 Windows 11。在大多数测试中,它们都连接到同一台显示器:一台通过 HDMI 连接的 120 Hz LG C1。我手头有 Radeon GPU,计划特别用它们来测试 gamescope,但这得等到下一轮测试了。应用设置均选择以避免硬件瓶颈。我的目标是轻松地在 120 Hz 输出上达到 120 FPS,并检测软件栈中是否有任何排队效应。
软件方面,我使用了 KDE Wayland 6.6.4、Proton-GE 10-33、MangoHud 0.8.2(用于晚限帧)、Nvidia 595.58.03。最初我也打算对比 X11 会话,但由于 KDE 即将移除 X11(https://blogs.kde.org/2025/11/26/going-all-in-on-a-wayland-future/),我放弃了这个计划。在 Windows 上,我交替使用 Nvidia 控制面板或 RTSS 进行帧率限制。
尽管工具是自动化的,但启动和整理测试运行仍然工作量巨大。控制所有变量非常麻烦,而且在测试过程中我经常发现新情况,这导致之前的测量结果作废。几个异常行为的例子:
- LG webOS 会在同一端口连接另一台电脑时切换“黑色帧插入”功能。
- 使用 KDE Konsole 标记测试开始会触发巨大的 `wl_shm` 呈现表面,这些表面通过 PCIe 复制到 GPU VRAM 需要很长时间。这会让合成器对刚刚飙升的时间产生过度悲观的预估。
- 在特定游戏中切换 V-Sync 模式后,更改不会立即生效。
## 合成测试
作为快速验证和显示设置的简单测试,我构建了自己的延迟测量工具(https://farnoy.dev/tools/mouse-latency)。它只是一个黑色的方块,点击后会立即变白;非常适合工具做出反应。我添加了一个可配置的延迟来模拟输入处理。测试在使用默认设置的干净 Chromium 配置文件中运行。
如何阅读图表
每张图表变化一个参数,显示在 Y 轴上。延迟是横向的:底部 X 轴以毫秒为单位表示,顶部以 120 Hz 下的帧数为单位。图表可能有多个面,每个面对应一个变量值。在每个面内,每个 Y 值可以有多个水平箱线图——每个箱线图对应其他可调参数的一种组合。同一 Y 行内的颜色相同,意味着只有 Y 参数在不同箱子之间改变。你可以悬停在每个箱子上,或查看图例,了解它对应哪些设置。每个条形图是基于多个样本的 IQR 箱线图,带有最小-最大须线和代表中位数的竖线。
结果完全符合预期——中位数和最小值大致按延迟量偏移。但是,为什么我的台式机比笔记本慢?它们运行的是从同一 NixOS 配置复用的相同软件版本,硬件也非常相似。我预期它们会一致,或者至少台式机会略有优势。为了进一步缩小差异,我在台式机上创建了一个全新的用户账户,重新运行测试:
问题就在这里——我的台式机用户配置文件中存在某些因素,导致了至少 3 毫秒的延迟!从这里开始,我尝试了很多方法:使用 plasma-manager(https://github.com/nix-community/plasma-manager)来对比现有配置和干净配置,移除所有虚拟桌面,禁用所有 KWin 特效和任何显示缩放。在随机关闭应用程序的过程中,我找到了罪魁祸首:Zed 编辑器(https://zed.dev/)。显然,即使打开的 Zed 窗口在后台空闲,也会给所有其他应用增加延迟。幸运的是,这不影响全屏游戏。我是在测量完其他所有内容后才确定这个问题的,所以很高兴这个发现没有使我的游戏内测量结果作废。稍后在 KWin 部分(https://farnoy.dev/posts/linux-latency#kwin-deep-dive)会有更多细节。
## LG 显示设置
接下来,我测试了电视上的各种设置。
将输入模式设置为 PC(会锁定大量图像设置)没有产生任何影响,而“黑色帧插入”看起来正好增加了一帧的延迟。这一点真的很可惜,因为我非常喜欢用它。看来它们的实现增加了额外的缓冲,尽管完全可以用无滞后滚动扫描(https://blurbusters.com/faq/oled-motion-blur/)来实现。
HDR 有一个微小但可测量的效果。
我计划测试 HDMI 自动低延迟模式——当源端请求时,显示器应该应用其低延迟设置。当我以前在 Windows 上日常使用 Radeon GPU 时,我记得驱动会在所有全屏应用中无条件启用 ALLM。我不得不通过伪造 EDID 来阻止它将该模式视为支持。Linux 似乎不支持 ALLM,而且我也没有在 Nvidia 的 Windows 驱动中找到对应选项。
## 游戏测试
我希望找到一款支持所有三大图形 API 的游戏,这样可以在它们之间进行对比。确实有少数这样的游戏,通常基于虚幻引擎,但它们除了一个 API 外,其余都描述为实验性的。我最终测试了三款游戏,每款都有可重复的测量设置。跨游戏比较没有意义,因为它们的动画时序各不相同。相反,重点在于每个 API 可用的不同可调参数。
### 毁灭战士:永恒(Vulkan)
这个很容易设置——只需加载一个黑暗关卡(随便哪个),开启无限弹药,对着黑暗墙壁观察重型加农炮的枪口闪光。游戏使用 Vulkan,因此在 Linux 上不需要翻译层。我无法让它直接在 Wayland 上运行,尽管这个问题去年已经修复了(https://github.com/GloriousEggroll/proton-ge-custom/releases/tag/GE-Proton10-8)。
一开始,平台之间唯一的区别是 Linux 上更宽的尾部(p75 处):
如果我们不将 FPS 限制在刷新率以下,启用 V-Sync 时就会开始缓冲帧。禁用 V-Sync 可以恢复这部分延迟,如下一张图所示。这仍然不会产生画面撕裂,因为游戏是通过 XWayland 运行的。
VRR 本身不是一个显著因素:
我测试的 Nvidia Windows 专属设置也并非如此:
### 无主之地 3(DX11, DX12)
我修改了存档,去掉了武器的弹匣配件,这样它就不会消耗弹药。这使得连续进行 500 次枪口闪光测量变得非常理想。
Windows 的延迟一直更低,尤其是在使用 V-Sync 时:
改用原生 Proton Wayland(`PROTON_ENABLE_WAYLAND=1`,在图表中显示为 `proton_wayland`)可以在这些情况下挽回一些延迟:
DX12 在两个操作系统上始终较慢。可能有些虚幻引擎的 hack 可以改善这一点,比如 `OneFrameThreadLag` 这个 CVar,但我没有测试。
我尝试了各种平台和 API 特定的开关:
- Windows 上的 Nvidia 超低延迟模式
- DX12 的 `VKD3D_SWAPCHAIN_LATENCY_FRAMES=1` 和 `VKD3D_SWAPCHAIN_IMAGES=2`
- DX11 的 `DXVK_CONFIG="d3d9.maxFrameLatency=1;dxgi.maxFrameLatency=1"`
唯一有影响的是 `VKD3D_SWAPCHAIN_LATENCY_FRAMES=1`,但即便如此,它仍然明显落后于 DX11:
限制 FPS 不让帧在刷新率标记处排队,能带来最大的改善:
### 哈迪斯 2(DX12)
这款游戏的动画启动时间较长,但一致性还不错。测量结果与之前测试的行为类似。在所有条件相同的情况下,以下设置有帮助,但不一定具有累加效应:
1. 将帧率限制在/低于刷新率。
2. 使用 wine_wayland / `PROTON_ENABLE_WAYLAND=1`。
3. 设置 `VKD3D_SWAPCHAIN_LATENCY_FRAMES=1`——不过,在使用固定刷新率的 wine_wayland 时,这会将帧率限制为刷新率的一半。
### 总结与建议
我的结论是优先使用 wine_wayland,采用晚限帧,在 DX12 游戏中设置 `VKD3D_SWAPCHAIN_LATENCY_FRAMES=1`,如果游戏无法达到稳定目标或帧时序不佳则启用 VRR。我真的很希望能把启用 V-Sync 和非 VRR 时的结果压得更低,但不知道如何实现。看起来,XWayland 的介入会破坏某些信令机制,导致在 FPS 等于刷新率且启用 V-Sync 时,交换链缓冲更多帧。
## 通过网络游戏
所有这些实验都是在《无主之地 3》(DX11)上完成的;结果可以与之前显示的本地测试进行比较。这是两个主机之间直接的 2.5GbE 网络,RTT 约为 0.3 毫秒。使用 `# tc qdisc replace dev $DEVICE root netem delay ${DELAY}ms` 增加出口延迟。典型网络具有对称延迟,但我也想确认哪个特定方向会影响延迟,所以我也测试了非对称场景。
首先,使用 USB/IP 在计算机之间发送输入,但显示输出直接从运行游戏的主机捕获:
结果完全符合预期。在没有注入网络延迟的情况下,结果与本地一样快。如果你想将硬件放在地下室(https://youtu.be/NwXAIGmwC4I),USB/IP 应该是一个不错的解决方案。这样你可以节省大量有源 USB 线缆或高级扩展坞的费用?
然后,我测试了通过 Moonlight 路由输入,但仍然捕获主机的直接视频输出,而不是编码后的流:
这证实了在网络发送输入方面,Moonlight 与 USB over IP 相当。这里也没有有意义的跨平台差异。在 Sunshine 主机上增加出口延迟没有产生影响——Moonlight 会持续发送最新的输入,而不会等待确认。
接下来,我测试了典型的往返流程:点击 → Moonlight → Sunshine → Moonlight → 显示器。在这里,我遇到了内核 7.0 的一个近期回归问题(https://www.spinics.net/lists/netdev/msg1180491.html),表现为视频流始终无法启动,但有一个简单的变通方法。
最后,我跨平台进行比较,在 Windows 上运行 Moonlight,同时在这些场景中保持 Sunshine 在 Linux 主机上运行:
总体而言,Windows 提供了稍微更灵敏的体验。部分差距可能可以用下一节关于 KWin 的内容来解释。但我无法解释网络延迟的影响——看起来 Windows 在这次测试中进行了“时间旅行”。在仅使用 Moonlight 转发输入的实验中观察到的长尾现象也令人惊讶。
## KWin 深入挖掘
为什么 KWin 比 Windows 的 DWM 慢?为什么当一个客户端提前排队呈现帧时,会对另一个客户端及其窗口不公平?这不符合我对合成器在固定刷新率显示器上工作原理的认知模型。在我看来,来自客户端的事件要么按时到达,在即将到来的帧中呈现,要么就会错过。但为什么两个小的桌面应用程序不能在同一时间间隔内一起合成呢?
为了理解这一差距,我向 KWin 添加了检测代码,并在运行延迟测量工具测试(https://farnoy.dev/tools/mouse-latency)的过程中捕获了一帧。
%3b%7d%23mermaid-0 .section%7bstroke:none%3bopacity:0.2%3b%7d%23mermaid-0 .section0%7bfill:hsl(52.9411764706%2c 28.813559322%25%2c 58.431372549%25)%3b%7d%23mermaid-0 .section2%7bfill:%23EAE8D9%3b%7d%23mermaid-0 .section1%2c%23mermaid-0 .section3%7bfill:%23333%3bopacity:0.2%3b%7d%23mermaid-0 .sectionTitle0%7bfill:%23F9FFFE%3b%7d%23mermaid-0 .sectionTitle1%7bfill:%23F9FFFE%3b%7d%23mermaid-0 .sectionTitle2%7bfill:%23F9FFFE%3b%7d%23mermaid-0 .sectionTitle3%7bfill:%23F9FFFE%3b%7d%23mermaid-0 .sectionTitle%7btext-anchor:start%3bfont-family:arial%2csans-serif%3b%7d%23mermaid-0 .grid .tick%7bstroke:lightgrey%3bopacity:0.8%3bshape-rendering:crispEdges%3b%7d%23mermaid-0 .grid .tick text%7bfont-family:arial%2csans-serif%3bfill:%23ccc%3b%7d%23mermaid-0 .grid path%7bstroke-width:0%3b%7d%23mermaid-0 .today%7bfill:none%3bstroke:%23DB5757%3bstroke-width:2px%3b%7d%23mermaid-0 .task%7bstroke-width:2%3b%7d%23mermaid-0 .taskText%7btext-anchor:middle%3bfont-family:arial%2csans-serif%3b%7d%23mermaid-0 .taskTextOutsideRight%7bfill:%232c2c2c%3btext-anchor:start%3bfont-family:arial%2csans-serif%3b%7d%23mermaid-0 .taskTextOutsideLeft%7bfill:%232c2c2c%3btext-anchor:end%3b%7d%23mermaid-0 .task.clickable%7bcursor:pointer%3b%7d%23mermaid-0 .taskText.clickable%7bcursor:pointer%3bfill:%23003163!important%3bfont-weight:bold%3b%7d%23mermaid-0 .taskTextOutsideLeft.clickable%7bcursor:pointer%3bfill:%23003163!important%3bfont-weight:bold%3b%7d%23mermaid-0 .taskTextOutsideRight.clickable%7bcursor:pointer%3bfill:%23003163!important%3bfont-weight:bold%3b%7d%23mermaid-0 .taskText0%2c%23mermaid-0 .taskText1%2c%23mermaid-0 .taskText2%2c%23mermaid-0 .taskText3%7bfill:hsl(28.5714285714%2c 17.3553719008%25%2c 86.2745098039%25)%3b%7d%23mermaid-0 .task0%2c%23mermaid-0 .task1%2c%23mermaid-0 .task2%2c%23mermaid-0 .task3%7bfill:hsl(180%2c 1.5873015873%25%2c 35.3529411765%25)%3bstroke:white%3b%7d%23mermaid-0 .taskTextOutside0%2c%23mermaid-0 .taskTextOutside2%7bfill:lightgrey%3b%7d%23mermaid-0 .taskTextOutside1%2c%23mermaid-0 .taskTextOutside3%7bfill:lightgrey%3b%7d%23mermaid-0 .active0%2c%23mermaid-0 .active1%2c%23mermaid-0 .active2%2c%23mermaid-0 .active3%7bfill:%2381B1DB%3bstroke:white%3b%7d%23mermaid-0 .activeText0%2c%23mermaid-0 .activeText1%2c%23mermaid-0 .activeText2%2c%23mermaid-0 .activeText3%7bfill:%232c2c2c!important%3b%7d%23mermaid-0 .done0%2c%23mermaid-0 .done1%2c%23mermaid-0 .done2%2c%23mermaid-0 .done3%7bstroke:grey%3bfill:lightgrey%3bstroke-width:2%3b%7d%23mermaid-0 .doneText0%2c%23mermaid-0 .doneText1%2c%23mermaid-0 .doneText2%2c%23mermaid-0 .doneText3%7bfill:%232c2c2c!important%3b%7d%23mermaid-0 .doneText0.taskTextOutsideLeft%2c%23mermaid-0 .doneText0.taskTextOutsideRight%2c%23mermaid-0 .doneText1.taskTextOutsideLeft%2c%23mermaid-0 .doneText1.taskTextOutsideRight%2c%23mermaid-0 .doneText2.taskTextOutsideLeft%2c%23mermaid-0 .doneText2.taskTextOutsideRight%2c%23mermaid-0 .doneText3.taskTextOutsideLeft%2c%23mermaid-0 .doneText3.taskTextOutsideRight%7bfill:lightgrey!important%3b%7d%23mermaid-0 .crit0%2c%23mermaid-0 .crit1%2c%23mermaid-0 .crit2%2c%23mermaid-0 .crit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d%23mermaid-0 .activeCrit0%2c%23mermaid-0 .activeCrit1%2c%23mermaid-0 .activeCrit2%2c%23mermaid-0 .activeCrit3%7bstroke:%23E83737%3bfill:%2381B1DB%3bstroke-width:2%3b%7d%23mermaid-0 .doneCrit0%2c%23mermaid-0 .doneCrit1%2c%23mermaid-0 .doneCrit2%2c%23mermaid-0 .doneCrit3%7bstroke:%23E83737%3bfill:%23E83737%3bstroke-width:2%3b%7d
相似文章
在Linux上测量输入延迟:X11与Wayland、VRR和DXVK
一篇详细的技术博客文章,作者构建了一个自定义硬件设备来测量Linux上的端到端输入延迟,比较了X11与Wayland、VRR开/关以及DXVK配置在游戏中的表现。
探究Linux图形系统(2025年)
深入探究Linux图形栈,从GPU三角形绘制出发,经过Mesa3D、GLFW、OpenGL、Vulkan、Wayland和Linux DRM,理解整个系统的工作原理。
Linux游戏性能提升,因为Windows API正成为Linux内核功能
随着Windows特定的同步API通过NTSYNC驱动直接实现在Linux内核中,Linux游戏性能得以提升。该驱动现已默认加载于Steam Deck上,有助于提高游戏兼容性和速度。
多用户 Wayland 现状
对 Linux Wayland 合成器和库中多座位(多个鼠标/键盘)支持的深入调查,并提供了已发布的工具和补丁以改善多用户计算。
实测 OpenCode 与自托管 LLM 的协作:Qwen 3.5、3.6、Gemma 4、Nemotron 3、GLM-4.7 Flash - v2
一位开发者在 RTX 4080 上用 OpenCode 对多款自托管 LLM(Qwen 3.5/3.6、Gemma 4、Nemotron 3、GLM-4.7)进行两项编码任务基准测试,揭示了速度与质量的权衡。