我们差点被自己的跑分"骗了":OrcaTerm 从落后 17 倍到 6 项第一,只用了两刀

11 阅读8分钟

我们差点被自己的跑分"骗了":OrcaTerm 从落后 17 倍到 6 项第一,只用了两刀

起因:一次"好得不真实"的跑分。结尾:10 项测试 6 项第一,滚动场景屠榜。中间:发现我们的 benchmark 一直在"作弊"——而作弊的,是我们自己写的队列。

一、先讲一个"高兴了三分钟"的故事

那天团队里有人惊呼:OrcaTerm 的 vtebench 跑分出来了,有几项居然比 Warp 还快。

要知道 vtebench 是业界测终端渲染性能的标尺:固定、可复现的 VT/ANSI 输入,测从输入到处理完成的耗时。Warp 和 Ghostty 是什么水平?公认的终端性能天花板。我们一个刚做客户端不久的产品,跑赢了?

大家高兴了大概三分钟。然后有人发现不对劲:

  • 测试经常跑不完,中途就停了;
  • 跑的过程中屏幕会长时间空白;
  • 最终产出的数据量明显偏少。

一个能"跑赢 Warp"的终端,为什么会跑不完测试?

二、反转:作弊的是我们自己的队列

顺着"屏幕空白"往下挖,挖出来一个藏得很深的问题。

流量控制模块里有个 maxQueueBytes=8MB 的队列上限。高压场景下,它触发了一串灾难性的连锁反应:

  1. 一次较长卡顿后,xterm 暂停渲染,数据停止送往渲染侧;
  2. 待处理数据持续堆积,超过 8MB 后,队列把最早的 chunk 扔了;
  3. 关键来了——这些被扔掉的数据之前已经收到 ACK,后端认为它们早就交付完成;
  4. 于是屏幕残缺,但测试探针提前命中结束条件,跑出一个被大幅低估的耗时。

翻译一下:终端丢了一部分内容没画,跑分却显示它"更快"。

这就像外卖系统显示"订单已完成",其实取餐区空间不够,最早的几份餐被清掉了。顾客拿到的托盘是空的,系统却已经结单。你要是只看"出餐速度",数字当然漂亮——但这成绩是假的。

ACK 语义一旦被队列丢弃破坏,上层协议的正确性就无从谈起。 这比慢更可怕:慢你看得见,假你看不见。

我们把队列上限一路调到 64MB,测试终于稳定跑完、数据完整。然后,真实基线出来了——

OrcaTerm 与 Warp、Ghostty 差距:3 到 17 倍。

刚才的"跑赢",只是一个 bug 送给我们的幻觉。

三、找瓶颈:不是画得慢,是消息碎成渣

修好尺子,开始量差距。桌面端是 Tauri 架构:Rust 后端管 PTY/SSH 连接和数据,React 前端用 xterm.js 渲染。一条终端输出链路被我们切成六段:

[0] PTY 读取 → [1] Rust 数据处理 → [2] Tauri IPC 传输
            → [3] JS 收包 → [4] 流量控制排队 → [5] xterm 渲染

前半段 [0]–[2] 跑在 Rust 线程,后半段 [3]–[5] 跑在前端 WebView 线程,两段并行、靠反压信号互相影响——这点很关键,后面会考。

三层埋点跑完,数据说话:

环节占比/数值说明
后端反压等待65.0%前端消费不过来,后端在干等
前端入队63.3%onmessage 是第一热点
xterm 渲染时延median 86.6ms从 write 到画完
每帧字节约 1KB8KB 缓冲,但每次 read 只吐约 1KB
1MB 数据帧数约 1025 帧即约 1025 次 IPC + 1025 次 onmessage
有效吞吐703 KB/s低得离谱

怕埋点骗人,又用 Safari Web Inspector Timelines 做了独立验证。三条轨道同时作证:

  • Network(蓝):密集蓝条铺满,IPC 帧之间几乎没有间隙;
  • JavaScript(紫):紫色竖条密不透风,全在处理 onmessage;
  • Layout & Rendering(绿):稀稀拉拉几根绿条,大片空白——渲染被活活挤没了;
  • CPU 常年 120%+。

真相浮出水面:问题不在"画得慢",在"消息碎成渣"。 Rust 侧每次只发约 1KB 的小帧,1MB 数据要经历约 1025 次 IPC 往返。主线程忙着拆快递,xterm 渲染连时间片都抢不到;前端一慢,反压又把后端摁住。就像后厨每做完一小口菜就单独跑一趟前台,前台忙着签收零碎包裹,反而没时间把菜端上桌。

四、第一刀:合批——1025 帧打到 18 帧

方向明确:把碎消息合并了再发。参数试了很多轮,最后定两条规则:

  1. 攒够 64KB 立刻发——帧数暴降,又不会单帧大到压垮渲染;
  2. 超过 5ms 强制发——小数据不许躺在队列里。

但这里有个差点翻车的坑。 只在读取循环里检查超时是不够的:读取阻塞等数据时,小数据会被卡死。症状特别诡异——粘贴一段文本,只显示第一个字符,敲一下空格才补全。

解法是让 PTY 读取和 5ms 定时器并行竞速,谁先到谁触发发送。高吞吐时安心合批,低吞吐时小数据也绝不滞留。

战报:

指标优化前优化后变化
1MB IPC 帧数约 10251857 倍下降
平均每帧字节约 1KB约 57KB57 倍提升
有效吞吐703 KB/s9,209 KB/s13 倍提升
后端反压等待65.0%52.3%再降 12.7pt

分场景耗时(ms):

场景优化前优化后
dense_cells(密集字符写入)413.934.3
scrolling_fullscreen(全屏滚动)2711.296.8
scrolling_top_region(顶区滚动)2187.257.8
sync_medium_cells(同步语义输出)2522.0108.6

一刀下去,大部分场景快了 90% 以上。

五、第二刀:调两个参数,又快一个数量级

合批打完,反压等待还有 52%——前端只要慢一下,后端立刻被摁停。为什么?

反压阈值从"保护机制"变成了"断流制造机"。 原阈值 500KB,而合批后单帧已经 57–64KB——也就是说,前端只要晚消费不到 10 帧,后端就被暂停。刚吃饱的链路,又被自己掐了脖子。

两个参数,各改一个数量级:

  • 反压阈值:500KB → 10MB(约单帧的 175 倍,给前端留足消费缓冲);
  • FIFO 渲染队列:8MB → 50MB(实测单轮 vtebench 总输出约 132MB/192s、峰值堆积远低于 50MB,xterm 内部也是 50MB 队列——覆盖峰值、不再丢包,继续加大边际收益趋零)。

有意思的是:xterm 渲染时延在两轮优化中始终纹丝不动(约 80ms)。第二刀砍的全是链路和反压段——这轮优化根本没碰渲染本身。找对瓶颈,比瞎优化重要得多。

再上报一次:scrolling 从 576ms 干到 25.9ms,各场景再降 74%–98%。

六、终局战报:10 项测试,6 项第一

两刀之后,与优化前比:整体提升 13 到 206 倍(scrolling_top_small_region 约 206 倍),p50 普遍进入 8–20ms 区间。

横向对比(vtebench P50,ms,越低越好)。数据来自 OrcaTerm 官方已公开发布的性能测试文章,各终端版本与测试环境以原文为准):

场景OrcaTermGhosttyWarpWezTermiTerm2Terminal.app
scrolling20.7124.8041.0671.0042.10151
scrolling_bottom_region14.9840.6820.9764.37559.39132
scrolling_fullscreen20.5537.4626.5887.81162.88173
scrolling_top_region16.0627.4166.8987.142812.75135
dense_cells8.896.597.029.8795.7721
unicode48.816.3013.3799.1083.155

10 项中 6 项第一、8 项前三,scrolling 场景全部第一。 滚动恰恰是构建日志刷屏时用户体感最强的部分——从"卡成 PPT"到"屠榜"。

七、五条可以抄走的经验

  1. 先修尺子,再量差距。 尺子本身在说谎时,所有优化都是玄学。
  2. 双重独立证据。 埋点 + Timeline 互相印证,才敢下结论;两条线程流水线是并行的,层内占比不能跨层相加。
  3. 合批三件套:大小 + 超时 + 竞速。 缺一样都会有回归——我们缺竞速时,粘贴只出首字符。
  4. 反压阈值必须和单帧尺寸保持数量级比例,阈值小于约 10 帧的量时,保护就变成断流制造机。
  5. 没动的指标会告诉你瓶颈在哪。 渲染时延全程 80ms 不变,说明这轮打中的是链路,下一仗才是渲染本身。

八、还没打完的仗

  • xterm 渲染侧仍是约 80ms,解码、OSC 解析、入队还是前端第一热点——评估 xterm.js 升级与渲染路径裁剪;
  • unicode 场景 p99 仍接近 400ms,偶发飞刺待收口;
  • 10MB / 50MB 是固定负载下的经验值,慢速 SSH 和本地 PTY 吞吐天差地别,下一步让参数随实测吞吐自适应浮动,而不是写死。

从"被跑分欺骗"到"揭穿自己"再到"两刀翻盘"——性能优化最有趣的从来不是最后那张战报表,而是中间那些差点让你走错方向的假象。

注:本文技术细节与测试数据整理自 OrcaTerm 团队已公开发布的研发复盘文章;"跑分失真"问题亦为官方原文中公开复盘过的历史 bug,相关细节以原文为准。

OrcaTerm 已提供 Web 端、桌面客户端和微信小程序,欢迎来 orcaterm.com 用你自己的构建任务和刷屏日志实测一把。