DeepSeek Harness 每轮结束多转 28 秒:turn/end 被 git 快照阻塞的链路
一次「你好」对话,模型 1.9 秒就说完了,界面却继续转圈到 30.6 秒——多出来的 28.7 秒不在模型,也不在网络,而在回合收尾的那条串行钩子里。 钩子做了一次完整的 git 工作区快照,而工作区里有 12 万个未被忽略的未跟踪文件(#10601)。按三段拆开看。
DeepSeek Harness 发生前:模型其实已经说完了
日志里的两个时间点把责任划得很清楚:内容在 1.939 秒就落盘了。
0.000s turn/start。1.939s assistant/message + step/end——模型输出完成,这一步结束。30.607s turn/end——回合才正式结束。- 中间 28.7 秒没有任何模型输出,纯粹是收尾等待。
- 这就是「界面还在转、但其实早就答完了」的来源。
DSH插件 发生时:串行钩子把 turn/end 挡在后面
agent/turn-stopping 是 serial 且 await 的分发,所以 turn/end 一定在它之后才写入。
agent.ts里在回合结束时判断没有下一步,然后await this.dispatch.serial('agent/turn-stopping', ...)。- 因为是串行等待,后续的 turn/end 写入被排在这之后。
- 钩子落点是
workspace-changes/src/index.ts,里面await recorders.get(...).stopping(turn)。 stopping()再落到recorder.ts的await snapshotTree(...)。- 快照最终执行
git add --all --ignore-errors,写一个私有 index。 - 整条链上没有任何一步是后台异步的,所以耗时直接叠加到用户等待上。
DeepSeek Harness 发生后:慢在未跟踪文件,不在受跟踪文件
git add --all 会为每个未跟踪文件计算 blob 哈希并写进 index,12 万个文件就是 12 万次计算。
- 社区在 Windows 11 + git 2.52.0.windows.1、1.6 万个受跟踪文件的仓库上做了对照测量。
- 受跟踪文件本身很快:
git ls-files只要 0.065 秒,git status --porcelain0.11 秒。 - 但
git ls-files --others --exclude-standard列出了 121,182 个未跟踪且未被忽略的文件。 - 其中一个临时目录贡献了 115,776 个、约 4.98 GB。
git add --all因此要 66.03 秒;git write-tree还要 1.65 秒。- 单次快照合计 67.7 秒——这解释了你看到的几十秒卡顿。
DeepSeek插件 一张表看清慢在哪一步
同一组命令,补 .gitignore 前后对比,差距全在未跟踪文件那一行。
| 操作 | 修复前 | 修复后 | 说明 |
|---|---|---|---|
git ls-files | 0.065s | 0.065s | 受跟踪文件很快 |
git status --porcelain | 0.11s | 0.11s | 只做 stat,不动 index |
git ls-files --others | 121,182 个 | 67 个 | 未跟踪文件数 |
git add --all | 66.03s | 0.13s | 差距来源 |
git write-tree | 1.65s | 0.04s | 随 index 变小 |
| 单次快照合计 | 67.7s | 0.17s | 卡顿消失 |
DeepSeek Harness 减负清单:先补 .gitignore
把构建产物、临时目录、缓存目录补进 .gitignore,是成本最低、收益最大的一步。
- 用
git ls-files --others --exclude-standard | wc -l数一下当前未跟踪文件。 - 按目录聚合,找出贡献最多的几个:
git ls-files --others --exclude-standard | cut -d/ -f1 | sort | uniq -c | sort -rn | head。 - 把
.dsh-build-tmp/这类构建临时目录、下载缓存、模型权重目录补进.gitignore。 - 重新数一遍,确认数量降到几十的量级。
- 手动跑一次
git add --all计时,确认从几十秒降到一秒以内。 - 再回 DSH 发一条消息,观察 turn/end 是否紧跟 step/end。
社区就是靠这一步把单次快照从 67.7 秒降到 0.17 秒的。
DSH插件 为什么「加缓存」「换硬盘」都不解决
成本与文件数量线性相关,不降数量就只能等。
- 慢的动作是为每个新文件算哈希,不是读磁盘缓存命中率。
- 换更快的盘能改善一部分,但 4.98 GB 的临时目录照样要遍历。
- 加大内存不改变
git add --all的算法复杂度。 - 结论:先减文件数,再谈硬件。
修复方向:把快照挪出阻塞路径
插件已经在监听 turn/end,说明「回合结束后再记」这条路本来就有。
- 在
turn-stopping里只记录需要的坐标(HEAD、基线 tree)。 - 把
snapshotTree改成回合结束后的异步补记,让 UI 的 running 立刻翻。 - 风险要认:这中间工作树可能又变,快照不再精确对应「回合结束那一刻」。
- 另一条路线是保住精确性、只把等待变可见:先发一个「正在生成变更摘要」的状态。
- 后者不改耗时,只是别让它看起来像模型还在思考。
注意事项
- 别只看总耗时。拆成 step/end 与 turn/end 两个时间点,责任立刻清楚。
- 别先怀疑模型。这段等待里没有任何生成。
.gitignore要覆盖临时目录,不只是源码构建产物。- 注意
--ignore-errors会让超长路径被静默跳过,快照可能不完整。 - 长会话里这是常态开销,每轮都要付一次,值得一次性治理。
如果你的 turn/end 延迟只有一两秒,那可能只是正常的收尾开销;几十秒才说明工作区里有大量未跟踪文件。相关排查见 dsh 启动很慢、启动卡住怎么办?DeepSeek Harness 启动耗时的原因与加速排查。
常见问题
不是,这是 DeepSeek Harness 的回合收尾钩子拖住了界面。会话日志里 assistant/message 与 step/end 早在 1.939 秒就写入了,turn/end 到 30.607 秒才落,中间 28.7 秒都花在收尾钩子上。
这 28 秒花在 DeepSeek Harness 的一次完整 git 工作区快照上。agent/turn-stopping 是串行且 await 的钩子,workspace-changes 在里面调 snapshotTree(),后者执行 git add --all,会把所有未跟踪文件也算一遍 blob 哈希。
因为慢的不是受跟踪文件,而是 DeepSeek Harness 快照要扫的未跟踪文件。社区实测仓库只有 1.6 万个受跟踪文件,但工作区里有 12 万个未被 .gitignore 覆盖的未跟踪文件,其中一个临时目录就贡献了 11.5 万个、4.98 GB。
有效,社区把相关目录补进 DeepSeek Harness 仓库的 .gitignore 后,未跟踪文件从 121,182 降到 67,单次快照从 67.7 秒降到 0.17 秒,卡顿随之消失。
因为 DeepSeek Harness 的成本来自要为每个未跟踪文件计算 blob 哈希并写入 index,属于工作量问题,不是缓存命中问题。文件数量不降,单次快照就是那么久。
相关术语
- agent/turn-stopping
- agent/turn-stopping 是回合结束前分发的串行钩子,必须在 turn/end 写入之前完成。任何在它里面同步等待的工作都会直接拖长整轮收尾时间。— https://github.com/deepseek-ai/deepseek-harness/discussions/10601
- snapshotTree
- snapshotTree 是 workspace-changes 用来记录工作区快照的函数,内部通过 git add 与 write-tree 生成一次完整树状态。它的耗时与工作区文件数量正相关。— https://github.com/deepseek-ai/deepseek-harness/discussions/10601
- git add --all
- git add --all 是把工作区所有变更(含未跟踪文件)加入索引的 git 命令。它会为每个新文件计算 blob 哈希,因此未跟踪文件数量是主要成本来源。— https://github.com/deepseek-ai/deepseek-harness/discussions/10601
- turn/end
- turn/end 是标记一个回合正式结束的会话事件。前端据此关闭运行状态,因此它被阻塞时,界面会一直显示模型仍在工作。— https://github.com/deepseek-ai/deepseek-harness/discussions/10601
来源
- #10601 — workspace-changes: git snapshot in agent/turn-stopping can block turn/end· deepseek-ai(GitHub Discussions)
- deepseek-ai/deepseek-harness(源码仓库)· GitHub