Linux 上 Chromium 软件图形后端:Mesa llvmpipe、SwiftShader 与 ANGLE GL 实测比较
有窗口运行时,前者的 CPU 使用量比后者低约 49%;默认 headless 模式下两者接近,迁移可用两个参数完成。
BotBrowser Team
没有 GPU 的 Linux 服务器仍需要软件光栅器,才能让 Chromium 提供可用的 WebGL。通过 ANGLE 有两个实用选择:SwiftShader,以及通过 ANGLE OpenGL 后端访问的 Mesa llvmpipe。BotBrowser 的测量使用虚拟显示器(Xvfb)上的有窗口会话,运行约 25 秒 Canvas 2D 绘制和 WebGL2 着色器。该负载下 SwiftShader 约使用 999% CPU,Mesa llvmpipe 约使用 513%。CPU 约减少 49%,同时 WebGL1、WebGL2 和 WebGPU 适配器仍然可用。
TL;DR
- 在有窗口的 Xvfb 测量中,SwiftShader 约用 999% CPU,Mesa llvmpipe 约用 513%,约减少 49%,同时 WebGL1、WebGL2 与 WebGPU 适配器仍然可用。
- 默认 headless 模式下两者分别约 713.5% 和 713.3%,此时迁移换来的是能力稳定性,而不是 CPU。
- llvmpipe 路径只有两个参数
--use-angle=gl --bot-gpu-emulation=false,前提是安装libgl1-mesa-dri、libglx-mesa0、libegl-mesa0和libvulkan1。 - 单独使用
--disable-gpu会让requestAdapter()返回 null,并使getContext('webgl')与getContext('webgl2')失败;两者同时存在时它还会覆盖--use-angle=gl。 - Ubuntu 22.04 上约 153% 的 lavapipe 一行会失去 WebGL2 和 WebGPU 适配器,因此它的低 CPU 数值不可直接比较。
目录
- 测量结果
- SwiftShader 为何更耗 CPU,以及三种后端的区别
- 单独使用 --disable-gpu 为何会移除 WebGL 和 WebGPU 适配器
- 两个参数的 llvmpipe 配置
- 光栅器输出差异与 seed 可复现性
- Linux 集群的 canary 主机部署
测量结果
这个结果有明确边界。它来自 Ubuntu 22.04 x86_64 主机上的一次虚拟显示器有窗口负载,使用 Windows 11 profile,汇总所有 Chromium 进程的 ps CPU,并对两次运行取平均。它不代表所有负载或主机。在默认 headless 模式下,两个后端的 CPU 几乎相同。
| 模式 | 后端 | CPU 均值 | 含义 |
|---|---|---|---|
| Xvfb 有窗口 | SwiftShader | ~999% | 基线 |
| Xvfb 有窗口 | Mesa llvmpipe | ~513% | 比基线低约 49% |
| Xvfb 有窗口 | Mesa lavapipe | ~153% | WebGL2 已禁用,不适合生产 |
| 默认 headless | 使用完整 unsafe 参数集的 SwiftShader | ~713.5% | 基线 |
| 默认 headless | Mesa llvmpipe | ~713.3% | CPU 大致相同,内存峰值约高 100 MB |
lavapipe 这一行是警告,不是推荐。它的低 CPU 来自会失去 WebGL2 和 WebGPU 适配器的配置,因此不能和另外两行直接比较。
请按实际运行模式阅读表格。如果流水线中有任何有窗口环节,例如用于截图、人工检查或旧 headless 路径的虚拟显示器,那么 llvmpipe 的 CPU 优势是实际的。如果已经使用默认 headless,不要期待 CPU 下降,迁移的价值在于稳定提供 WebGL、WebGL2 和 WebGPU,而且不依赖 --enable-unsafe-* 参数。 关于有窗口与无窗口会话之间应比较什么,请参阅 Headless 与 Headed 浏览器 Profile 一致性,其中介绍了如何记录受支持的组合并对差异分类。
按部署形态整理后如下:
| 部署形态 | CPU 预期 | 迁移理由 |
|---|---|---|
| 虚拟显示器上的有窗口会话 | llvmpipe 更低,处于本次测量范围 | 节省 CPU,参数更少 |
| 旧 headless 路径或以有窗口方式运行的截图流水线 | llvmpipe 更低,请用自己的负载验证 | 节省 CPU,面向未来的兼容性 |
| 默认 headless 模式 | 与 SwiftShader 大致相同 | 不使用 unsafe 参数也能稳定提供 WebGL 和 WebGPU |
| 暂时冻结的部署 | 迁移前不变 | 在下一次 Chromium major 升级前规划迁移 |
SwiftShader 为何更耗 CPU,以及三种后端的区别
SwiftShader 是 CPU 软件光栅器,在软件中实现 OpenGL ES 和 Vulkan。每次着色器调用都在 CPU 核心上执行,这正是它的设计目标。它自包含,不需要系统 Mesa 软件包,因此很多 Linux 集群默认使用它。
999% 表示负载运行时大约占用了十个 CPU 核心。将这个数字乘以一台主机上的实例数量,渲染后端就会占据很大的 CPU 预算。测量展示了后端之间的差异,但没有隔离一个后端更便宜的具体原因,因此不对内部实现作额外推断。
Mesa 将 llvmpipe 说明为使用 LLVM 在运行时生成代码、并在多个 CPU 核心上多线程运行的软件光栅器。在没有硬件 GPU 的服务器上,当 ANGLE 指向系统 OpenGL 栈时,Mesa 会自动选择它。
ANGLE 是 Chromium 将 WebGL 调用转换为原生图形 API 的层,--use-angle 参数选择目标 API。这个选择决定最终执行工作的软件光栅器。共有三条路径:
- 通过 ANGLE 使用 SwiftShader 是历史 fallback。新版 Chromium 将它置于
--enable-unsafe-swiftshader后面,名称中的 unsafe 表明它已不是默认路径。 - 通过 ANGLE GL 使用 Mesa llvmpipe 使用
--use-angle=gl。ANGLE 调用系统libGL,没有硬件 GPU 时 Mesa 将其绑定到 llvmpipe。WebGL1、WebGL2 和 WebGPU 入口仍可用,配置中没有标记为 unsafe 的参数。 - 通过 ANGLE Vulkan 使用 Mesa lavapipe 是 Vulkan 软件光栅器。它需要 ANGLE 为 WebGL2 期待的 Vulkan 扩展,而不是所有 Mesa 版本都提供这些扩展。
| 后端 | 所需参数 | WebGPU 适配器 | WebGL1 | WebGL2 | 面向未来的兼容性 |
|---|---|---|---|---|---|
| Mesa llvmpipe(推荐) | --use-angle=gl --bot-gpu-emulation=false | 有 | 有 | 有 | 有 |
| SwiftShader | --disable-gpu --enable-unsafe-swiftshader --use-gl=angle --ignore-gpu-blocklist | 有 | 有 | 有 | 已弃用且受 unsafe 参数保护 |
| Mesa lavapipe,bare metal,Ubuntu 22.04 | --use-angle=vulkan --enable-features=Vulkan,DefaultANGLEVulkan,VulkanFromANGLE | 无(null) | 有 | 无 | Ubuntu 24.04 及更高版本可用 |
| Mesa lavapipe,Docker,任意发行版 | 同上 | 无(null) | 无 | 无 | 不适合生产 |
单独使用 --disable-gpu | 仅 --disable-gpu | 无(null) | 无 | 无 | 只有配合额外参数才可用 |
每一行都存在 navigator.gpu 属性。WebGPU 一栏表示 navigator.gpu.requestAdapter() 是否返回可用适配器。 关于适配器如何暴露以及会泄露什么,请参阅 WebGPU 指纹识别:下一代 GPU 追踪,其中介绍了适配器细节及其控制方式。
lavapipe 的 CPU 数字很诱人,因此值得单独说明。在 Ubuntu 22.04 中,打包的 Mesa 23.2 没有 ANGLE 为 WebGL2 所需的全部 Vulkan 扩展。bare metal 上 WebGL1 仍可工作,但 WebGL2 失败,WebGPU 适配器为 null。在 Docker 中 Vulkan ICD 加载也不稳定,lavapipe 经常完全无法启动,WebGL1 也会失效。Ubuntu 24.04 搭配 Mesa 24.2 或更高版本改善了 bare metal 情况,但容器行为目前仍不可靠。在主机升级到较新 Mesa 后再复查 lavapipe,在已测试配置上暂时不要把它当作生产选项。
上游项目清楚描述了各自的角色。ANGLE README 将 ANGLE 定义为把 OpenGL ES 调用转换到 Vulkan、桌面 OpenGL 等图形后端的层,因此只需一个 --use-angle 值就能在三种光栅器之间切换 Chromium。SwiftShader 仓库描述了基于 CPU 的 Vulkan 图形 API 实现,可提供硬件独立性。Mesa 文档将 llvmpipe 列为 Mesa 维护的 Gallium 软件驱动之一。这些资料共同说明 llvmpipe 是受支持的软件路径,而不是临时绕过方案。
SwiftShader 参数还涉及时间规划。Chromium 已将这条路径标记为 unsafe,说明它可能不会长期保留,但目前没有公开移除日期。如果参数将来被删除,依赖它的流水线会失去 WebGL,因此提前迁移可以把被迫切换变成计划内变更。
单独使用 --disable-gpu 为何会移除 WebGL 和 WebGPU 适配器
许多 Linux Chromium 教程建议使用 --disable-gpu,把它当作服务器渲染问题的一行修复。在当前 Chromium 中,单独使用该参数会禁用 WebGPU 适配器发现和两个 WebGL 上下文。navigator.gpu 属性仍存在,但 requestAdapter() 返回 null,getContext('webgl') 和 getContext('webgl2') 都失败。Canvas 2D 仍然工作,所以问题往往要等到页面需要 WebGL 时才会暴露。
许多生产部署在 --disable-gpu 上叠加逃生参数来绕过这个问题:
--disable-gpu
--enable-unsafe-swiftshader
--enable-unsafe-webgpu
--use-gl=angle
--ignore-gpu-blocklist
这组参数各有独立作用。--enable-unsafe-swiftshader 保持 SwiftShader 路径可用,--enable-unsafe-webgpu 保持软件 WebGPU 可用,--use-gl=angle 强制 ANGLE 连接,--ignore-gpu-blocklist 跳过 Chromium GPU 黑名单。llvmpipe 路径不需要它们。
这五个参数能让 WebGL1、WebGL2 和适配器恢复,但也把部署绑定到 SwiftShader,以及四个名称含有 unsafe 或 ignore 的参数。每次 Chromium 升级都需要重新确认这些参数的行为。
还有一个陷阱:无论有窗口还是 headless,--disable-gpu 的优先级都高于 --use-angle=gl。如果加入 Mesa 参数却保留 --disable-gpu,llvmpipe 永远不会生效,看起来就像迁移没有作用。
最干净的做法是删除 --disable-gpu 和四个逃生参数,整体替换为下一节的两个参数。能够说出五参数方案、两参数方案,并解释单独 --disable-gpu 为何移除适配器发现的操作者,就掌握了完整迁移过程。
两个参数的 llvmpipe 配置
先安装让 ANGLE 访问 llvmpipe 的 Mesa 用户空间软件包:
sudo apt-get update
sudo apt-get install -y \
libgl1-mesa-dri \
libglx-mesa0 \
libegl-mesa0 \
libvulkan1
然后用两个参数启动。默认 headless 不需要显示器:
chromium-browser \
--headless \
--no-sandbox \
--user-data-dir="$(mktemp -d)" \
--bot-profile="path/to/profile.enc" \
--use-angle=gl \
--bot-gpu-emulation=false
在虚拟显示器上运行有窗口会话时,去掉 --headless 并提供显示器:
DISPLAY=:10.0 chromium-browser \
--no-sandbox \
--user-data-dir="$(mktemp -d)" \
--bot-profile="path/to/profile.enc" \
--use-angle=gl \
--bot-gpu-emulation=false
--use-angle=gl 让 ANGLE 经过系统 OpenGL 栈,主机没有硬件 GPU 时 Mesa 会加载 llvmpipe。--bot-gpu-emulation=false 让真实 GL 驱动渲染 WebGL 输出,同时保留 Canvas 2D 噪声和 --bot-noise-seed 的变化层。这个参数范围有意保持狭窄,不会关闭 profile 或其他指纹保护。
--bot-gpu-emulation 接受三个值。false 是本次迁移的设置,true 是默认标准模式,priority 为同一浏览器实例中大量并发上下文的负载增加优先 GPU 调度。它独立于 GL 后端选择,当 false 将 GPU 工作交给真实驱动后便不需要 priority。
迁移已有部署时,先删除这些参数,再加入两个新参数。它们中的一些会与 --use-angle=gl 冲突,任何一个都可能在没有报错的情况下抵消迁移。
| 要删除的参数 | 原因 |
|---|---|
--disable-gpu | 禁用适配器发现和两个 WebGL 上下文,并优先于 --use-angle=gl |
--enable-unsafe-swiftshader | 只用于保持 SwiftShader 可用 |
--use-gl=swiftshader | 显式选择 SwiftShader,已由 llvmpipe 替代 |
--use-gl=angle | 旧形式已由 --use-angle=gl 替代,可能与之冲突 |
--ignore-gpu-blocklist | 不需要,因为 llvmpipe 不在黑名单中 |
--enable-features=Vulkan,DefaultANGLEVulkan,VulkanFromANGLE | 固定 lavapipe 路径,在 Ubuntu 22.04 bare metal 上禁用 WebGL2 |
--use-angle=vulkan | 强制使用同一个 lavapipe 路径 |
--disable-vulkan-surface | 仅与 Vulkan 路径有关 |
在容器镜像中加入同样四个 Mesa 软件包,并在启动时传入同样两个参数。使用 --shm-size=2g 会为 Chromium 提供更多共享内存,提高稳定性。请记住,容器中出问题的是 lavapipe,因此容器部署更应该通过 ANGLE GL 使用 llvmpipe。
保留 --headless、已有的 --no-sandbox、所有 --bot-* 参数(包括 --bot-profile 和 --bot-noise-seed),以及网络和 --user-data-dir 设置。在 Playwright 或 Puppeteer 启动时,将同样参数放进 args 列表:
const browser = await chromium.launch({
executablePath: 'path/to/botbrowser',
args: ['--use-angle=gl', '--bot-gpu-emulation=false', '--bot-profile=path/to/profile.enc'],
});
光栅器输出差异与 seed 可复现性
不同软件光栅器对同一个着色器会产生不同像素输出。从 SwiftShader 切换到 Mesa llvmpipe 后,readPixels 或 toDataURL 返回的精确字节会改变。这是两个光栅器的预期差异,不代表迁移失败。
页面读取为身份的信息仍由 profile 控制。User-Agent、平台、未屏蔽的 vendor 和 renderer 字符串来自 profile,不会随后端变化。--bot-noise-seed 的行为也不变:在所选后端内,相同 seed 会复现相同输出,不同 seed 会产生不同但稳定的输出。
因此有两条实践规则。按后端分别保存参考截图,不要比较 SwiftShader 与 llvmpipe 的字节。迁移前后比较时使用同一个后端和同一个 seed,避免预期的光栅器差异掩盖真实回归。
Linux 集群的 canary 主机部署
先在一台 canary 主机验证,再修改其余集群。以下步骤让每一步都可观察:
- 在 canary 主机安装
libgl1-mesa-dri、libglx-mesa0、libegl-mesa0和libvulkan1。 - 在一个实例中把 GPU 参数组替换为
--use-angle=gl --bot-gpu-emulation=false并重启,因为参数只在启动时读取。 - 确认 WebGL1 和 WebGL2 上下文可以创建,并且
navigator.gpu.requestAdapter()返回非 null 适配器。 - 打开
chrome://gpu,确认 WebGL、WebGL2 和 WebGPU 行显示通过 ANGLE 和 OpenGL 的软件渲染,而不是 disabled。该页面还会显示活动的 ANGLE 和驱动字符串,可确认正在使用 llvmpipe。 - 在同一有窗口或 headless 模式、同一负载下,将 CPU 与匹配的 SwiftShader 基线比较。
- 重复相同 seed 和不同 seed 的检查,然后推广到其余集群。
下表将常见症状对应到通常原因。
| 症状 | 常见原因 | 修复 |
|---|---|---|
| 适配器为 null,两个 WebGL 上下文都失败 | 单独使用 --disable-gpu,或 Docker 中的 lavapipe | 删除 --disable-gpu 和 Vulkan 参数,只保留两个参数 |
| WebGL1 可用,但 WebGL2 和适配器为 null | 旧 Mesa 的 bare metal lavapipe | 删除 Vulkan 参数,切换到 --use-angle=gl |
libGL error: failed to load driver: swrast | 缺少 Mesa 用户空间软件包 | 安装 libgl1-mesa-dri、libglx-mesa0 和 libegl-mesa0 |
chrome://gpu 仍显示 SwiftShader | 某个参数再次强制它,例如 --use-gl=swiftshader | 删除该参数并重启进程 |
| CPU 没有下降 | 默认 headless,负载本身占主导 | 这是预期结果,价值在于稳定性和面向未来的兼容性 |
| WebGL 像素输出改变 | 使用了不同光栅器 | 这是预期结果,为新后端重建参考截图 |
只有匹配的比较才能让 CPU 数字可信。两个后端使用同一主机、profile、负载和模式。运行足够长的负载,例如约 25 秒 Canvas 2D 绘制加 WebGL2 着色器。汇总该实例所有 Chromium 进程的 CPU,至少重复两次并取平均,避免主机短暂负载决定结果。一个模式中的节省不能推断另一个模式,因此应在实际交付模式下运行基线。 关于如何跨后端审查 Canvas、WebGL 和 WebGPU 输出,请参阅 不同渲染后端的浏览器隐私验证,其中介绍了渲染矩阵和受控的测试顺序。
也要预期 CPU 可能不变。在默认 headless 模式,任一后端都会将渲染写入离屏缓冲区,因此 Canvas 和 WebGL 工作本身占主导,两个后端成本大致相同。迁移仍能带来能力稳定性和更小的参数集,而且不依赖 unsafe 参数。
推广到其余机群前,canary 记录里要留下三样可核对的东西:chrome://gpu 上报告的活动 ANGLE 与 GL 驱动字符串、包含 --use-angle=gl --bot-gpu-emulation=false 的实际启动行、以及同一 seed 下的参考截图。同时记下能力上限:BotBrowser 不能让 llvmpipe 超过主机能力,不能保证默认 headless 一定节省 CPU,不能让不同光栅器产生完全相同的 WebGL 像素字节,也不控制 Chromium 上游弃用 SwiftShader 的时间表;--bot-gpu-emulation 需要 ENT Tier 2。
主机准备请阅读 headless 服务器配置指南 和 Docker 部署指南。后端之间的 seed 行为请阅读 noise seed 可复现性。完整参数参考见 Linux GPU 后端部署指南。
公开来源
实践结论
先删掉 --disable-gpu 和四个逃生参数,安装四个 Mesa 包,再在一台 canary 主机上用 --use-angle=gl --bot-gpu-emulation=false 启动,确认无误后再动其余集群。随后在实际发布的模式里重跑同一段 25 秒负载:有窗口模式约 49% 的节省并不能说明默认 headless,同时保留两次会话的 seed 对比,让光栅器输出差异始终可见。
本文属于指南: 浏览器部署与发布:运维决策地图