浏览器的主线程很昂贵

查看原文 HN 讨论

文章摘要

一篇由韩国工程师 kciter 撰写、配了十几个可交互 demo 的长文(原文韩语,作者同步发布英文版)。它把「前端性能优化」的注意力从大家熟悉的网络请求、bundle 体积、缓存,扭转到一个经常被忽略的稀缺资源上:浏览器主线程。

主线程在做什么。 作者把它的工作分成两类。一是跑 JavaScript——我们写的代码、事件处理器、定时器、网络响应回调、框架内部逻辑全在这里,按入队顺序执行,与屏幕刷新周期无关。二是画屏幕——DOM 或样式变化后,浏览器依次执行 requestAnimationFrame 回调、样式计算、布局(reflow)、绘制(paint),只有最后的合成步骤交给合成器线程。如果什么都没变,前面这些步骤会被整个跳过。

关键在于这两类工作排在同一条队里。JS 是单线程事件循环模型,一个任务运行时其他什么都做不了。60Hz 屏幕每帧约 16.6ms,扣掉浏览器自身开销后实际预算约 10ms,120Hz 直接砍半。一个跑 200ms 的函数就意味着 200ms 内既不能重绘也不能接收点击——超过 50ms 的任务通常就被认定为 long task。作者指出,INP 和 TBT 这两个 Web 性能指标,本质上就是在度量主线程被阻塞了多久。

作者把对策分成两大家族。

家族一:把主线程的时间用好(四招)

家族二:干脆不用主线程

作者回答了开篇 demo 埋的伏笔:主线程被完全阻塞时,CSS 动画为什么还在转?因为它压根就没跑在主线程上。浏览器里主线程之外还有合成器线程(合成已绘制的图层,负责滚动和某些动画)、光栅线程、以及我们能显式创建的 worker 线程。但我们不能随意指挥它们——合成器和光栅是浏览器自管的地盘,而能创建的 worker 又有无法访问 DOM 这个大限制。所以「不用主线程」不等于把任意工作扔出去,而是挑出能被改写成其他线程能处理的形式的那部分工作。

最后一招:把工作本身消灭掉。 作者给了三条路:丢弃(实时日志这类流过即弃的数据,处理跟不上就默默扔掉最旧的,用户不会察觉,跟上当下比展示全部更重要)、合并(排行榜这类只有最新值有意义的数据,把积压更新合并成只应用最终值,这样工作量被钉死在屏幕能消化的范围内,无论流入多快)、跳过(针对重复工作而非流入工作,同样输入给出同样结果就没必要算第二次,也就是记忆化)。作者点破:这个思路其实贯穿全文——debounce 是跳过打字期间的执行,feed demo 是跳过不可见帖子的渲染,「这篇文章有一半其实是在讲消灭工作」。

结语部分作者说,学优化时注意力容易跑到「怎么把活干好」上,但最大的收益通常来自移除工作;在让某个任务变快之前,先问一句:这活到底需不需要干、需不需要现在干、需不需要在这里干?

HN 评论精华

423 分、149 条评论。讨论分成几条清晰的支线,其中有一条相当刺眼。

一、「你根本不该需要这些技巧」派 vs「有些站点真的需要」派

ahartmetz 开了个火药味十足的头:「如果你那个有文字之类东西的网站不干蠢事和没必要的破事,那么那些不存在的、没必要却昂贵的代码在哪运行根本无所谓。」这条被大量点踩,也引出了整场最长的对线。another-dave 顺着调侃「如果你压根没网站,加载耗时就是零秒」,但他也补了一句有意思的观察:移动端开发社区一直非常在意「别占用主线程」,而这是他第一次在 Web 开发领域看到同类文章。Tepix 举 HN 和 earth.nullschool.net 作对比反问:你是说所有动画和渲染都没必要吗?ahartmetz 澄清自己的意思是很多网站用的技术量远超实际需要。

dspillett 给出了讨论中最公允的调停:如果页面部分是艺术品而非信息转储,或者出于目的必须做交互式渲染(编辑器、实时状态显示、游戏和交互玩具),那这些技术就是必要的,这篇文章就有用。当 manphone 说「那种站点不到 1% 但这套技术被广泛部署得多」时,dspillett 又反过来说了句很到位的话:「『不是真的需要就别这么干』这句话说了等于没说,所以才被点踩——已经足够明智的人不需要听,而不明智的人也不会听。这条评论就是噪音,跟它抨击的那些没必要的动画一样没必要。」adzm 的态度类似:这文章给出的是当你确实需要图片、动画、高频更新时该怎么办的好综述,并说自己学到了「React 用 postMessage 到 MessageChannel 来让出主线程」这个知识点。

nicoburns 插了个反直觉的点:文字渲染其实相当昂贵,只是页面不大时在现代机器上看不出来。ahartmetz 回应说他反而惊讶于浏览器把文字渲染做得这么便宜——他知道这事不容易,浏览器是真的做得极好,所以纯文字渲染很少成为问题。

二、真正的瓶颈是 bundle 和 hydration,不是交互

martinald 提出了最有分量的技术性异议:文章太聚焦于交互了,现实中 90% 以上的慢站点不是慢在交互,而是慢在它们发了巨大的 React/Next.js bundle 和极重的 hydration 工作。他见过很多超过 10MB 的 bundle 需要下载、解析、hydrate,甚至见过多个 SPA 叠在同一个站里;在慢网络或慢 CPU 上,页面几十秒基本不可用,hydrate 之后再怎么 yield 也救不回来。他后面还补充说,即便 bundle 很小,hydration 也可能极差,并自嘲「看得出来我被 Next.js 的 bundle 优化留下心理阴影了吧」。

这条引出了 SPA 存废之争。jcelerier 挑战说:拿一台 Amazon 畅销榜前十的廉价 HP 笔记本、配千兆光纤上网一周,再来说这是不是问题(言下之意 CPU 才是瓶颈)。cryptonym 主张回到 HTML、重新评估每一个第三方脚本、加缓存。doix 给出了权衡:慢网络下频繁交互时他反而更喜欢正经的 SPA,尤其买东西的时候,宁可初次加载久一点然后一切都顺——但读一篇博客文章时预加载一切就毫无意义,理论最优是首屏服务端渲染加渐进增强的混合站点。phoghed 泼了盆冷水:这些人对过去有过于玫瑰色的看法,现在慢又难用的站点在服务端渲染 + jQuery 面条代码的年代同样慢同样难用——「提交一个巨大表单结果拿到个随机错误,整个状态全丢」,那在过去极其常见。cryptonym 反驳说状态丢失在 SPA 上照样发生、而且没有反馈的随机错误恰恰是典型的 SPA 体验,并搬出 RUM 数据(HTTP Archive 技术报告)说这不只是管理层的问题。

RetroTechie 贡献了一段很具体的慢网络苦水,落点是一条值得裱起来的 UI 原则:在处理某个 UI 元素的代码就绪之前,不要把这个元素呈现出来。他描述的是 cookie 弹窗——页面加载好、你开始读,然后脚本弹出 cookie 对话框把一切冻住,滚不动、按钮没反应、原本能读的内容还被压暗;20 多秒后终于看到「接受/拒绝/设置」,但按了都没用(除了那个「接受」经常比「拒绝」和「设置」响应更快的暗黑模式)。他指出开发者机器快网速快,可能根本意识不到这个问题。kevin_thibedeau 的做法更彻底:用 NoScript,站点坏了就白名单几个可能必需的域名,试几次不行就走人;看到十几个域名根本懒得管——「把没用的 JS 拿掉之后,Web 快多了」。

三、虚拟滚动的副作用

mschuster91 对文章推荐的「屏幕外只留空壳」做法极为不满:「所有这么干的人都去死吧,说的就是你 Reddit、Outlook Web、Bluesky」——这让浏览器自带的页内搜索(Ctrl+F)彻底没法用,而这些平台自己的搜索功能又蠢得要命。prodigycorp 反问那你怎么解决 DOM 塞满一堆东西带来的性能损失,并指出站点其实可以自己实现搜索来弥补,只是它们不做,但怪虚拟化是不对的。charcircuit 认为解法应该是改进浏览器去掉这个性能损失,现代计算机极其强大,没有理由处理不了海量 DOM。mr_toad 给了个有意思的观察:浏览器渲染一百万个 DOM 元素快到你察觉不到,问题是你一秒钟要做几百次;讽刺的是,正是对流畅度和帧率的执念才制造了这个问题。mschuster91 自己给出的答案是:Outlook Web 里邮件列表的一「行」嵌在字面意义上约 30 层 div 的迷宫里,性能必然崩;00 年代的电脑处理几千封邮件的收件箱、滚动都毫无问题,而 Outlook 连保持 30 封在活动内存里都费劲——表格式的东西就该用 table 渲染,而不是一堆假装自己是表格的 div,因为那条路径已经被优化到死了。

四、「这是 AI 写的吗」的争论

这是本次讨论里最不愉快的一支。Lebenita 抛出「AI;DR」——内容看起来有价值,但为什么不自己动手写;shevy-java 更直接:「所以这是 AI 垃圾?为什么有人觉得我想读 AI 垃圾邮件?」还阴阳了一句「哇,还有这么多吹捧账号,这是 HN 的新玩法吗?」DuckConference 说自己一读就觉得像 AI,丢给 Pangram 检测也认同,然后感慨「一堆人吹得跟氧气似的……死他妈的互联网」。

ricardobeat 站出来反问:大家到底在这篇文章里看到了什么 slop 的迹象?还是仅仅因为看到一大段文字就条件反射? 他说自己入行很久,看到的是一篇仔细、正确、平静的文章,措辞和句式都没有明显的 AI 痕迹,只是有点长——可能是受 ciechanow.ski 那种风格启发。Lebenita 列了五个他认为可疑的句式片段作为「证据」,并承认作者母语可能不是英语,但坚称「这不是翻译腔,这是 AI 写作腔」。ricardobeat 回击说这是过度解读,那几段完整段落里没有 AI 的痕迹(而他每天要读数千行 AI 生成文本),这些不是常见的 tell,就是写得不错而已,读起来就像十年前所有写得好的技术文章,Quillbot 给出的 AI 生成概率是 0%。

最有力的澄清来自 prodigycorp:「我要给所有说这是 AI slop 的人一个统一回复:这篇文章是用韩语写的,然后翻译成英文。把它叫 slop,这才是判断力 slop。」duskdozer 顺势指出了一个更微妙的现象——「不知从什么时候起,『机器翻译』和『机器翻译 + LLM 润色』被混为一谈了」,他个人觉得用 Firefox 自带的「翻译此页」读韩语原文反而更顺。cafebabbe 的表态或许最有代表性:「老实说……我平时也是 AI;DR 阵营的,但这篇是真的好 :/」

五、零散但有价值的技术补充