我们刚完成修改,我运行了 git commit,Zed 立刻卡住了,开始出现 沙滩球…,10秒后才恢复正常。
我再次尝试:修改,git commit,又出现了沙滩球。不妙。
Zed 理应很快。到底怎么回事?
我们打开 Instruments,运行了一个 CPU 性能分析。

嗯…我们的进程在…什么都没做…?这不合理。至少 Instruments 同意这是一个“严重卡顿”。
Mikayla 搜索了一下,找到了 这篇文章,建议我们尝试“线程状态”工具。

这很有趣,主线程确实被阻塞了(几乎所有后台线程也是)。但为什么呢?
偶然发现左侧下拉菜单中的“OS Fundamentals”子工具,我们找到了答案。

它卡在系统调用 psynch_cvwait 中。这是一个条件变量等待,但它在等待什么呢?我们的后台线程似乎也什么都没做…
通过点击最右下角的图标查看堆栈跟踪,我们得到了一点线索,但这更像是“不应该发生的事情”。

看起来主线程正在有意地阻塞在 block_with_timeout 中。我们在几个地方这样做,以确保如果能快速完成用户可见的任务,结果会在下一帧显示。话虽如此,超时设置为 5ms,这样如果任务花费的时间比预期长,我们不会阻塞 UI 线程。在这种情况下,我们阻塞了 4550ms,这,查看记录,大概是太长了1000倍。
把这个问题放到一边:为什么主线程在 5ms 后没有被唤醒?我们继续在 Instruments 中点击。
查看右下角的 Narrative 视图,我们得到了第一个真正的线索。

“线程被 git 唤醒。”有趣。考虑到我是用 git commit 触发的,git 在运行并不奇怪,但它会阻塞 Zed 就很奇怪了。
通过 git 过滤 Narrative 视图,我们发现了一个更有趣的事情。看起来几乎每次我们的应用程序被唤醒时,都是被 git 唤醒的,不仅如此,每次都是不同的 git 进程。我们有 257 个 git 进程在运行?!

我们在这个领域最近添加的功能是 内联 Git Blame,所以它是主要嫌疑人。果然,禁用它就解决了问题。
从那时起,追踪问题就相对容易了。当我们添加 git blame 代码时,是为了为 gutter 提供支持,因此假设它一次会在一两个文件中打开。我们设置了一堆事件监听器,以便如果 git 索引更改,责怪信息就会更新。
不幸的是,由于 git blame 现在为每个文件都启用了,并且我们打开了 257 个文件,所以每次我通过提交更改 git 索引时,Zed 都会同时生成 257 个 git 进程。哎呀!(现已修复…)。
这只剩下为什么主线程在 5ms 后没有被唤醒的谜团。我认为问题的一部分是争用——我们的进程树正在尽其所能地生成 git 进程——但这并不是全部。
为了在几毫秒后唤醒主线程,我们启动了一个后台任务,该任务休眠所需时间,然后(通过条件变量)向主线程发出信号以恢复。不幸的是,macOS 上的 GPUI 计时器以低于后台任务的优先级运行。经典的优先级反转:主线程正在等待优先级最低的任务,而该任务又被我们所有的 git 进程阻塞。又一个哎呀!(也已修复…)。
我们为 Zed 的速度感到非常自豪,当我们不小心让它变慢时,确实有点尴尬。话虽如此,深入挖掘并找出问题所在总是很有趣的。
这些修复已随 v0.133.7 发布,同时修复了我们使用 新监控工具 发现的一些其他卡顿问题(后续将发布博文!)。
如果您的 Zed 出现沙滩球卡顿,请提交问题,我们很乐意与您一起深入研究并找出问题所在。