← 返回博客

修复 Git Blame “沙滩球”问题

2024年5月6日


我们刚完成更改,我运行了git commit,Zed 立即卡住,开始出现“沙滩球”,10 秒后恢复。

我又试了一次:进行更改,git commit,出现“沙滩球”。糟糕。

Zed 应该很快。发生了什么?

我们打开了Instruments 并运行了 CPU 性能分析。

macOS Instruments showing a Severe Hang, and no CPU usage.
macOS Instruments 显示严重的卡顿,没有 CPU 使用率。

嗯... 我们的进程什么都没做...?这说不通。至少 Instruments 认为这是一个“严重卡顿”。

Mikayla 搜索后找到了这篇文章,建议我们尝试“线程状态”工具。

Thread State instruments showing the main thread is blocked.
线程状态工具显示主线程被阻塞。

嗯,这很有趣,主线程确实被阻塞了(实际上几乎我们所有的后台线程也是如此)。但是为什么呢?

偶然发现左侧下拉菜单中的“OS Fundamentals”子工具,我们找到了答案。

OS Fundamentals sub-instrument showing a psynch_cvwait syscall
OS Fundamentals 子工具显示 psynch_cvwait 系统调用。

它卡在系统调用psynch_cvwait中。这是一个条件变量等待,但它在等待什么?我们的后台线程似乎也没有做任何事情...

通过点击最右下角的图标查看堆栈跟踪,我们得到了一丝线索,但更像是“不应该发生的事情”。

Backtrace from the main thread
主线程的回溯

看起来主线程是有意阻塞的,在一个block_with_timeout中。我们在几个地方这样做,以确保如果我们可以快速完成用户可见的任务,结果会在下一帧显示。也就是说,超时设置为5ms,因此如果任务花费的时间超出预期,我们不会阻塞 UI 线程。在这种情况下,我们阻塞了4550ms,检查记录,这大约长了 1000 倍。

把这个问题放在一边:为什么主线程在 5 毫秒后没有被唤醒?我们继续在 Instruments 中点击。

查看右下角的 Narrative 视图,我们得到了第一个真正的提示。

The Narrative around the time the thread was unblocked
线程被解除阻塞时的 Narrative

“线程被 git 置为可运行状态”。有意思。鉴于我正在用git commit 触发此问题,git 正在运行并不令人惊讶,但令人惊讶的是它会阻塞 Zed。

git过滤 Narrative 视图显示了更有趣的内容。看起来几乎每次我们的应用程序被唤醒时,都是由 git 唤醒的,而且不仅仅是这样,每次都是不同的 git 进程。我们有 257 个 git 进程在运行?!

A large number of git process-swaps in the Narrative
Narrative 中大量的 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 出现“沙滩球”,请提交问题,我们很乐意与你一起深入研究并找出问题所在。