浏览器主线程为何如此昂贵
The Browser's Main Thread Is Expensive
提到前端优化,大家常想到减少网络请求或压缩包体积,却很少关注主线程。在交互密集的场景下,无论网络多快,一旦主线程被阻塞,屏幕就会卡顿。文章指出,代码本身可能并不慢,问题在于它占用了主线程。浏览器的主线程负责 JavaScript 执行、渲染和事件处理,所有工作都在单线程队列中串行执行。如果任务超过 16.6 毫秒的帧预算,就会导致掉帧。解决之道在于智能分配时间:通过拆分长任务、批处理、优先级排序和延迟执行,让主线程在任务间隙处理用户输入和渲染,从而恢复流畅体验。
代码本身并不慢,问题只是它恰好是占用主线程的那段代码。
HN 评论区
150- martinald
文章写得很好,真希望更多人知道这些内容。
不过问题在于,这篇文章过于聚焦于交互性。实际上,90% 以上运行缓慢的网站,慢的原因并非真的在于交互性,而是因为它们打包了巨大的 React/Next.js 包,并且需要执行极其繁重的 hydration(水合)工作。
有_太多_网站的包体积超过 10MB,需要下载、解析并进行 hydration。
我甚至见过(很多)网站内部嵌套了多个 SPA(单页应用)。
如果你的网络较慢和/或 CPU 性能较弱,页面基本上会几十秒内完全无法使用,此时无论如何在 bundle hydration 后让出主线程(yielding)都无法真正解决这个问题。
- jonathanlydall
文章太棒了。
对我来说几乎没有新信息,因为我基于对渲染“线程”如何工作以及如何阻塞的直观理解,已经自己应用过其中一些技术。但对于经验较少的开发者来说,这篇文章应该极具启发性,并能提供对正在发生之事的坚实理解。
我在大约 15 年前制作的一个业余项目 [0] 中就使用了 yield 技术,特别是当它需要在 canvas 上执行大量绘图操作时。我也曾尝试使用 worker 线程在后台线程上渲染部分组件,但当时无法在它们与主线程之间高效地复制数据,只能将数据作为 base64 编码的 PNG 发送,而编码、解码以及将其复制到 canvas 上的开销,导致其性能远不如直接在主线程上完成所有操作。
该网站还在 JavaScript 中对上传的文件进行 Gzip 解码,当时我找到的库是同步执行所有工作的,因此很容易让 UI 线程锁定 10 秒以上。我调整了它,使其能够每 200 毫秒左右让出一次控制权,这个过程非常有教育意义,尤其是它让我下定决心永远不要在 if 语句后省略大括号。我花了很长时间试图理解为什么它不起作用,直到最后我意识到我添加的一条语句不在 if 语句的块内。这并不是我不懂 if 语句的工作原理,而是我脑子里觉得缺少大括号也没关系……
- nerdralph
我同意文章中的大部分观点,但想澄清以下几点:
> 为了让屏幕看起来流畅,帧必须以显示器的刷新率绘制。在最常见的 60Hz 显示器上,这意味着每秒 60 帧,或每帧约 16.6 毫秒。
完全匹配显示器刷新率并非绝对必要。对于 144Hz 的显示器,72FPS 对绝大多数人来说看起来已经足够流畅,甚至 48FPS 对大多数人来说也很流畅。我同意更高的 FPS 更好,但收益是递减的。
- piker
写得真好,楼主。我们从事 WASM 开发,正在考虑使用 web workers 来辅助在该上下文中将文档渲染任务移出主线程。读到 ArrayBuffer 仅通过引用传递(moved by reference only)时,我感到很惊讶。这可能是一个可行的选项。感谢整理这篇文章。
- larodi
顶级文章,致敬!之前应用过其中一些技术,它们确实非常重要。问题在于,当你学习或教授 JS 时,通常剩下的时间很少来讨论这些话题,而这些话题的重要性往往比表面看起来要大得多。
协作式多任务处理的核心要点就是时不时地让出控制权(交还给调度器),以便它能处理绘图。此外,在 60fps 下,每 N 帧计算一次的内容非常多,而人眼察觉不到,动画依然流畅。
当涉及 WebGPU 时,情况会变得更加有趣,不过 CSS 动画器确实非常快。
注:我最近的一些作品(newskool 数字传单网站)-> bsf.hmsu.org // nouveauxhivers.dub4powder.xyz
- gwbas1c
顺便一提:这不仅仅是浏览器的问题。所有平台(Windows、Mac、iOS、Android)通常都采用单线程 UI。
- jkhdigital
这篇文章以如下陈述作为结尾:
> 开发中的很多事情都是权衡取舍,你必须根据具体情况做出选择,这最终取决于开发者的经验和判断。
这是一篇很棒的文章,但我认为它在将调度问题框架化为“经验和判断”的问题上有些滞后。将工作分配给稀缺资源的问题,是计算机科学中最古老、研究最透彻的问题之一。在试图重新发明轮子之前,去查阅一下相关教科书才是合乎逻辑的做法。
- skobes
关于 'FLIP(First, Last, Invert, Play)' 的讨论,如果提到 View Transitions API 会更好,这个 API 基本上就是为了自动化这项技术而存在的。