dsh-webui-perf
awa-123-cw
DeepSeek Harness WebUI 性能优化开关插件:长代码流式渲染/历史加载/高亮缓存优化,设置面板一键开关(with official-package patches)
PROJECT TOPICS
PROJECT README
DSH(DeepSeek Harness)大会话性能插件:零拷贝 fork、投影分片预热、分片 materialize, 一次装齐,消除 fork/历史加载对超大会话的事件循环阻塞。
⚠️ 版本兼容性警告:本插件通过 monkey-patch dsh 内部方法实现(
SessionStore.fork、PersistenceCoordinator.initFor、JsonlSessionPersistence.encodeMaterialization、SessionPreparations等),与 dsh 版本高度耦合,当前针对0.1.0-rc.6开发并验证。 dsh 升级后这些内部方法签名可能变化——所有补丁都带源码特征校验,不匹配时自动跳过 优化并回退官方行为(不会导致崩溃),但优化会静默失效。升级 dsh 后请确认启动日志 无signature mismatch告警,必要时重新适配本插件。
⚠️ 能力边界(重要):本插件只能缓解超大对话的性能/内存问题(分片、零拷贝、 LRU 裁剪、预热等),无法彻底解决。根本原因在 dsh 的架构——live 会话事件树全量 驻留内存(每个超大会话 ~700MB)、历史加载全量解码 + 逐事件深拷贝。这些是上游架构 问题,插件 monkey-patch 触及不到。真正的根治依赖 dsh 上游支持事件分页加载/按需驻留, 或主动控制会话规模(见下文「避免超长对话」)。
dsh 0.1.0-rc.6 在大会话(数十万事件)上存在三类同步阻塞,导致 fork 卡顿、
历史加载报 signal timed out (internal)、严重时 OOM:
| 问题 | 环节 | 实测 |
|---|---|---|
| A. fork 深拷贝 | Session 构造器逐事件 snapshotJsonValue(纯 JS 深拷贝)+ persistence initFor 的 structuredClone(seed) |
18.2MB/20k 事件合计 ~480ms 同步阻塞 |
| B. projection 冷折叠 | SessionProjectionRegistry.cellFor() 冷时同步 buildCell 全量折叠 |
74 万事件冷折叠阻塞 20+ 分钟(100% 单核) |
| C. fork 全量序列化 | fork 子会话首次落盘 encodeMaterialization → eventLines = map(JSON.stringify).join("\n") 一次性序列化整个 seed |
60 万事件 = 501MB 单字符串;74 万事件直接 RangeError: Invalid string length |
为什么「单个会话没事、fork 后出事」:普通会话持久化走增量 appendLines
(每次序列化几十个事件),而 fork 子会话是全新 id,走 materialize 全量序列化
整个 seed——这是唯一会一次性序列化整条日志的路径。
zeroCopyFork,A):fork 的 seed 事件本就是 deepFreeze
不可变纯 JSON 树。补丁改走 Session.prepare(..., { seedSource: 'persistence' })
的 fromRestore 通道——原地冻结复用引用,跳过整树深拷贝(346ms → 19ms)。
子会话 header(parentSession/seedLength/cwd)与官方 fork 逐字段一致。fastInitFor,A):PersistenceCoordinator.initFor 里那次
structuredClone(seed) 替换为冻结引用复用(135ms → ~0ms)。带 rc.6 源码特征
校验(structuredClone(e) 标记),内部结构不匹配时自动跳过并告警。warmupEnabled,B):会话进入(created/resume)且事件数超过
阈值时,抢在首次同步冷折叠前,分片重放 cells——每 chunkSize 个事件
setImmediate 让出事件循环,折叠完成后直写 registration.cells(WeakMap),
此后 snapshot()/drive() 全部命中热 cell。可用时从投影缓存行取基线跳过已折叠
前缀。实测 74 万事件:冷折叠 20 分钟 → 预热 200ms。header.parentSession 存在)
预热完成后立即 cache.write(child) 建立投影缓存行——否则它被放弃时永远没有
缓存行,下次打开历史 coldSnapshot 走 readFrom(0) 全量读。chunkedMaterialize,C):encodeMaterialization 每
materializeChunkEvents 个事件一个 zstd frame(多帧是 dsh 解码端
scanZstdFrames 的原生格式,字节兼容),消除单巨字符串与 RangeError。backfillOnBoot,B 辅助,默认关):磁盘缺缓存行的大会话流式
补写。默认关的原因:readRaw 的 zstd 全量解码是同步的、插件层不可分片,
大文件仍会冻结事件循环数秒~数十秒。preparedCacheTrim/preparedCacheSize,D):persistence
的冷会话 LRU 默认缓存 5 个完整事件树(每个大会话 ~700MB,5×700MB 叠加是 OOM
主因之一)。插件运行时把容量降到 preparedCacheSize(默认 1)并淘汰最旧的
ready 条目,主动释放冷会话事件树——省 ~2.8GB。无需手动改 cordis.patch.yml;
config.set 运行时改这两个键即时生效;dispose 恢复原 capacity(已淘汰条目
不复活)。heapWarnBytes,D):--max-old-space-size 是 V8 启动期
参数、进程内改不了。插件检测 heap 上限低于阈值(默认 6GB)时告警,并引导用
scripts/start-dsh.ps1(内置 --max-old-space-size=8192)重启。# 从 GitHub 安装(推荐)
dsh plugin --profile web add github:orangeofcarl0-sys/dsh-large-proj-perf
# 或
dsh plugin --profile web add https://github.com/orangeofcarl0-sys/dsh-large-proj-perf
# 本地开发
dsh plugin --profile web add file:<本仓库路径>
注意:每次修改仓库代码后,需把
lib/、cordis.patch.yml、package.json同步到<DSH_HOME>/profiles/web/node_modules/dsh-large-proj-perf/(file:安装 不会自动跟随源文件更新),或重新执行dsh plugin add。
重启 dsh web 生效。日志出现 [dsh-perf] installed (...) 即成功。
环境要求:Node ≥ 22.15.0(
node:zlib的 zstd 接口所需,低版本加载插件 会直接失败)。package.json已声明engines。
多个超大会话(数十万事件)的 live 事件树每个 ~700MB,默认 V8 heap 上限 ~4GB 会让 dsh 在内存叠加时 OOM。插件已自动做冷会话 LRU 裁剪(省 ~2.8GB),但 heap 上限是 V8 启动期参数、进程内改不了,推荐用仓库自带脚本启动:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\start-dsh.ps1
它等价于 node --max-old-space-size=8192 .../dsh/lib/bin.js web(停止旧进程 →
重启 → 打开浏览器)。若不使用该脚本,插件启动时会打 V8 heap limit ... < ...
告警提醒你加参数。
本插件做的是「被动优化」——把 fork/历史加载/落盘的大会话阻塞降下来,但无法削减 正在使用的 live 会话事件树(每个 ~700MB,dsh 架构决定全量驻留)。要从根上避免 超长对话的内存/卡顿,推荐配套使用:
/fresh 一键「总结当前对话 → 开启新对话 → 归档老对话」。在一个会话工作
一段时间后用它归档,释放 live 事件树内存,而不是让会话无限增长到 70 万+ 事件。dsh plugin --profile web add github:orangeofcarl0-sys/dsh-fresh-start
两者配合:dsh-large-proj-perf 兜底性能,dsh-fresh-start 主动控制会话规模。
POST http://127.0.0.1:3080/dsh-large-proj-perf/api/<method>:
stats.get — fork 次数/零拷贝占比/回退、预热计数、补行计数、最近记录stats.reset — 清零config.get / config.set — 运行时开关,config.set 同时写 settings 持久化;
数值项带下限钳制(如 materializeChunkEvents ≥ 1000、chunkSize ≥ 1),
非法值(类型不符/NaN/低于下限取下限)被拒绝或收敛,不会进入危险区间curl -X POST http://127.0.0.1:3080/dsh-large-proj-perf/api/stats.get
curl -X POST http://127.0.0.1:3080/dsh-large-proj-perf/api/config.set \
-d '{"zeroCopyFork": false}'
测试依赖真实的 dsh 内部包(@deepseek-ai/dsh-session 等),它们不在本仓库依赖里,
而是全局 dsh 安装的嵌套依赖,Node 解析不到。先链接再跑:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\link-deps.ps1
npm test # 或单独跑:node tests/smoke_fork.mjs
tests/smoke_fork.mjs(17 断言):官方 fork 基线 / 零拷贝 fork 功能等价 /
header 平价(含 origin 不继承)/ seed 前缀逐字节等价 /
dispose 还原 / 版本漂移回退 —— ALL PASStests/test_fast_initfor.mjs(8 断言):initFor 补丁安装 / 源码特征漂移跳过 /
无 persistence 服务存活 —— ALL PASStests/smoke_warmup.mjs(11 断言):分片预热与同步折叠一致 / checkpoint 基线 /
并发 drive 让位 / dispose 中止 —— ALL PASStests/test_backfill.mjs(8 断言):fork 回填 / 磁盘冷会话补行 —— ALL PASStests/test_chunked_materialize.mjs(5 断言):分片多帧解码等价 / 阈值不变 —— ALL PASStests/test_cache_trim.mjs(17 断言):LRU 裁剪 / dispose 恢复 capacity / 运行时
开关 / config.set 数值钳制 —— ALL PASS_forkSeed、
initFor/encodeMaterialization 源码特征、SessionPreparations.capacity 等)。
dsh 大版本升级后,特征校验会自动跳过优化并回退官方行为(不崩溃、不误补),
但优化会静默失效——升级后务必确认启动日志无 signature mismatch,并按需
重新适配。本插件不适合在 dsh 版本频繁变动时依赖其优化。enqueue 的逐事件 structuredClone(fork 第三次拷贝)在插件层无法安全消除——
它在 write-behind 闭包内部,且承担"persistence 独立于生产者"的所有权语义。根治需
上游改为按需快照。coldSnapshot 的全量 readFrom(0)(zstd 解码 + 逐事件
snapshotStoredEvents 深拷贝)无法在插件层安全分片——readRaw 的同步解码是硬伤。
根治需上游把 readFromCore/loadStored 改成分片让出事件循环。CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。