我们刚完成更改,我运行了git commit
,Zed 立即卡住,开始出现“沙滩球”,10 秒后恢复。
我又试了一次:进行更改,git commit
,出现“沙滩球”。糟糕。
Zed 应该很快。发生了什么?
我们打开了Instruments 并运行了 CPU 性能分析。

嗯... 我们的进程什么都没做...?这说不通。至少 Instruments 认为这是一个“严重卡顿”。
Mikayla 搜索后找到了这篇文章,建议我们尝试“线程状态”工具。

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

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

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

“线程被 git
置为可运行状态”。有意思。鉴于我正在用git commit
触发此问题,git 正在运行并不令人惊讶,但令人惊讶的是它会阻塞 Zed。
按git
过滤 Narrative 视图显示了更有趣的内容。看起来几乎每次我们的应用程序被唤醒时,都是由 git 唤醒的,而且不仅仅是这样,每次都是不同的 git 进程。我们有 257 个 git 进程在运行?!

我们在此空间中添加的最新功能是内联 Git Blame,因此它是主要嫌疑对象。果然,禁用它就消除了问题。
从那里开始,追踪问题相对容易。当我们添加git blame
代码时,是为了支持侧边栏,因此假设它一次只会在一两个文件中打开。我们设置了一堆事件监听器,以便在 git 索引更改时,blame 信息会更新。
不幸的是,由于 git blame 现在对每个文件都启用,而且我们打开了 257 个文件,因此每次我通过提交更改 git 索引时,Zed 都会同时生成 257 个 git 进程。哎呀!(现在已修复…)。
剩下的问题是为什么主线程在 5 毫秒后没有被唤醒。我认为这里的部分问题是争用——我们的进程树正在尽可能多地使用 CPU 来生成 git 进程——但这并不是全部原因。
为了在几毫秒后唤醒主线程,我们生成一个后台任务,该任务休眠所需的时间,然后(通过条件变量)向主线程发出信号以恢复。不幸的是,macOS 上的 GPUI 定时器以比后台任务更低的优先级运行。一个经典的优先级反转:主线程正在等待优先级最低的任务,而该任务又被我们所有的 git 进程阻塞。又一次哎呀!(也已修复…)。
我们为使 Zed 快速而感到自豪,当我们不小心使它变慢时,这有点令人尴尬。也就是说,深入研究并弄清楚发生了什么总是很有趣的。
这些修复程序已在 v0.133.7 中发布,同时还修复了我们使用新的监控工具发现的其他一些卡顿问题(博客文章即将发布!)。
如果你的 Zed 出现“沙滩球”,请提交问题,我们很乐意与你一起深入研究并找出问题所在。