前言¶
DSH(DeepSeek Harness)的設計理念是「一切皆插件」,社區裏已有一些性能方向的插件:@linxin666/dsh-perf 做觀測與前端渲染降載,dsh-pref-kit 從源頭做流式增量合併。但存量大會話有一類問題它們管不到:單個會話事件數累積到幾十萬之後,fork 子會話、重開歷史會話、把 fork 結果落盤,都會變成事件循環上的同步重活——輕則幾百毫秒的卡頓,重則單核跑滿 20 分鐘,甚至直接 RangeError。
本文介紹的 dsh-large-proj-perf(v1.2.0,MIT 許可證,作者 orangeofcarl0-sys)針對的就是這三類阻塞:零拷貝 fork、分片投影預熱、分片 materialize、冷會話內存治理,一次裝齊。下面按問題定位、實現方案、安裝運維的順序展開。
它解決什麼問題¶
dsh 0.1.x 在大會話上有三類同步阻塞,README 給出了源碼級定位和實測數據:
| 問題 | 環節 | 實測 |
|---|---|---|
| fork 深拷貝 | Session 構造器逐事件 snapshotJsonValue,加上 persistence initFor 的 structuredClone(seed) |
18.2MB / 20k 事件合計 ~480ms |
| projection 冷摺疊 | cellFor() 緩存冷時同步 buildCell 全量摺疊 |
74 萬事件阻塞 20 分鐘以上(單核 100%) |
| fork 全量序列化 | encodeMaterialization 一次性序列化整個 seed |
60 萬事件 501MB 單串;74 萬直接 RangeError |
根因都在 dsh 自身實現裏,插件層能做的是繞開或分片。下面逐項說明插件的做法。
核心功能¶
零拷貝 fork¶
fork 出來的 seed 本身是 deepFreeze 過的不可變 JSON 樹,逐事件深拷貝並非語義必需。插件把 fork 的 seed 改走 Session.prepare(seedSource:'persistence') 的 fromRestore 通道,原地凍結、複用引用,子會話 header 與官方實現逐字段一致。實測單次 fork 從 346ms 降到 19ms。
分片投影預熱與 fork 緩存回填¶
會話進入且事件數超過閾值(minEvents,默認 20000)時,插件搶在首次冷摺疊之前分片重放 cells,每片之間用 setImmediate 讓出事件循環,結果直寫 registration.cells;磁盤上已有投影緩存行時,取基線跳過已摺疊前綴。74 萬事件的會話從阻塞 20 分鐘降到約 200ms。
fork 出的子會話原本沒有投影緩存行,重開會走全量讀取(分鐘級);預熱完成後插件回填緩存行,重開時間降到秒級。
分片 materialize¶
fork 落盤原本一次性序列化整個 seed,60 萬事件就是一條 501MB 的巨字符串。插件改爲每 materializeChunkEvents(默認 50000)個事件寫一個 zstd frame;多幀格式是解碼端 scanZstdFrames 的原生格式,字節兼容,解碼側無需改動。
冷會話 LRU 裁剪與 heap 檢測¶
每個超大會話的 live 事件樹約 700MB,多個冷會話疊加時默認 V8 heap 上限(約 4GB)會 OOM。插件在運行時把 SessionPreparations.capacity 降到 preparedCacheSize(默認 1),並淘汰最舊的 ready 條目,實測省出約 2.8GB;config.set 即時生效,dispose 時恢復原 capacity。
配套的 heap 檢測會在 heap 上限低於閾值(默認 6GB)時告警,並提示加 --max-old-space-size。
fast initFor:按項退役的樣板¶
persistence 的 initFor 會對 seed 做一次 structuredClone,插件改爲凍結引用複用,實測 135ms 降到約 0ms。dsh 0.1.0-rc.8 起上游已原生實現同樣的零拷貝形態,該補丁自動退役——特徵缺失但檢測到上游零拷貝形態時只打 info,不誤報漂移。
這體現了插件的整體策略:上游吸收某項能力後,對應補丁按項退役而非整體下線。各補丁的實際狀態經 stats.get 的 patches 字段暴露(active / retired / inactive / off)。
dsh-std 標準兼容¶
插件提供 dsh-std Community v0.15 兼容面:dsh-plugin.json 清單加 facets.host.entry(指向 lib/std-host.js)。當前 dsh 0.1.x 走 cordis.patch.yml 加載 lib/index.js,行爲不變;未來標準宿主按清單加載,雙軌並行。
補丁安全機制¶
所有補丁通過 monkey-patch 內部方法實現,與 dsh 版本高度耦合。安全設計有三層:
1、每個補丁帶源碼特徵校驗,簽名不符自動跳過並告警,絕不盲補;
2、三層回退:能力探測 / try-catch / 配置開關,失敗自動回退官方實現;
3、dispose 完整還原,不殘留修改。
安裝與啓用¶
運行要求:Node ≥ 22.15.0(依賴 node:zlib 的 zstd 接口);dsh 0.1.0-rc.6 ~ 0.1.2-rc.1(package.json 的 engines 已聲明)。
先執行安裝命令,再重啓 dsh web,日誌出現 [dsh-perf] installed (...) 即成功:
dsh plugin --profile web add github:orangeofcarl0-sys/dsh-large-proj-perf
本地開發用 file: 協議安裝:
dsh plugin --profile web add file:<本倉庫路徑>
注意 file: 安裝不會自動跟隨倉庫改動,修改代碼後需把 lib/、cordis.patch.yml、package.json、dsh-plugin.json 同步到 profile 目錄,或重新執行 dsh plugin add。
推薦啓動方式¶
LRU 裁剪能省內存,但 heap 上限是啓動期參數,多個超大會話並存時默認的約 4GB 不夠。建議用倉庫自帶腳本啓動,內置 --max-old-space-size=8192:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\start-dsh.ps1
配置與運行時 API¶
全部配置可經 Settings 卡片、config.set API 或 settings 持久化修改,數值項帶下限鉗制(如 materializeChunkEvents ≥ 1000、chunkSize ≥ 1、preparedCacheSize ≥ 1)。主要配置項:
| 鍵 | 默認 | 說明 |
|---|---|---|
zeroCopyFork |
true |
零拷貝 fork |
fastInitFor |
true |
fast initFor(rc.8+ 自動退役) |
slowForkWarnMs |
100 |
fork 耗時告警閾值 |
warmupEnabled |
true |
大會話投影分片預熱總開關 |
minEvents |
20000 |
低於此事件數不預熱 |
chunkSize |
5000 |
每片摺疊事件數(下限 1) |
chunkYieldMs |
0 |
片間讓出方式:0=setImmediate,>0=setTimeout |
warmOnCreated |
true |
session/created 即預熱 |
backfillOnBoot |
false |
磁盤冷會話補投影緩存行(默認關) |
backfillMaxSessions / MinBytes / MaxBytes |
8 / 1MB / 32MB |
補行掃描範圍 |
chunkedMaterialize |
true |
分片落盤 |
materializeChunkEvents |
50000 |
每幀事件數(下限 1000) |
preparedCacheTrim |
true |
冷會話 LRU 裁剪總開關 |
preparedCacheSize |
1 |
裁剪目標容量(下限 1) |
keepRecent |
50 |
內存保留的最近記錄數 |
heapWarnBytes |
6GB |
heap 上限告警閾值 |
API 端點掛在 http://127.0.0.1:3080/dsh-large-proj-perf/api/<method>,僅接受迴環地址並有同源校驗。stats.get 返回 dshVersion 版本探針、patches 各補丁狀態、fork / 預熱 / 補行計數;stats.reset 清零計數;config.get / config.set 做運行時開關,config.set 同時寫 settings 持久化。
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 0.1.0-rc.6 / rc.7 / rc.8、0.1.1-rc.1 / rc.2、0.1.2-alpha.5、0.1.2-rc.1 上開發驗證。升級 dsh 後先做兩件事:
1、跑 node tests/verify_compat.mjs,對真實安裝的源碼做 16 項結構斷言;
2、啓動後確認日誌沒有 signature mismatch 告警——補丁不匹配時不會崩潰,但優化會靜默失效。
上游 rc.7 修復了歷史分頁棧溢出、rc.8 優化了 SQLite 後端,均與本插件不重疊,也沒觸碰根因(歷史加載全量解碼、live 事件樹全量駐留)。
與其他性能插件共存¶
| 插件 | 層面 | 與本插件關係 |
|---|---|---|
| @linxin666/dsh-perf | 觀測 + 寫批頻控 + 前端渲染降載 | 零方法重疊,它不 patch 方法 |
| dsh-pref-kit | 源頭流式增量合併(事件量 −11~56%) | 上下游互補:它減少新事件,本插件治理存量與 fork |
三者可同時安裝。唯一注意點:行管理實驗項的白名單都含 session-projection-cache,不要禁用該行,否則投影緩存回填與基線讀取失效(有防禦回退,不崩潰,但功能打折)。
適用場景與注意事項¶
適合的場景很明確:DSH 會話事件數上到幾十萬、fork 或重開歷史會話明顯變慢、或遇到過 OOM。會話規模小的用戶感知不大,預熱有 minEvents 閾值兜底。
安裝前有幾點必須知道:
1、插件以當前 dsh 進程的權限運行,且通過 monkey-patch 修改內部方法。裝之前建議先看一遍 GitHub 倉庫源碼和許可證(MIT)。
2、本插件是被動優化,無法削減正在使用的 live 事件樹。推薦配合同作者的 dsh-fresh-start:/fresh 一鍵「總結 → 開新會話 → 歸檔老會話」,主動控制規模;一個兜底性能,一個控制規模。
3、緩解不根治:live 事件樹全量駐留(約 700MB/超大會話)、歷史加載全量解碼的根因在 dsh 架構,根治依賴上游支持事件分頁加載 / 按需駐留。enqueue 的逐事件 structuredClone 和冷會話 coldSnapshot 的全量 readFrom(0) 也無法在插件層安全消除。
開發側如果想自己驗證:先運行 scripts/link-deps.ps1 鏈接 dsh 內部包,再 npm test(8 套件、112 斷言)。
小結¶
經過上面的步驟,fork、歷史加載、落盤三類阻塞都有了對應當法,各項獨立開關、帶特徵校驗與回退,上游吸收後按項退役。對被超大會話拖慢的 DSH 實例來說,這是一個成本很低、可隨時回退的緩解方案。
源碼與文檔見 GitHub 倉庫:https://github.com/orangeofcarl0-sys/dsh-large-proj-perf;社區目錄頁:https://www.skillhub.cn/plugins/orangeofcarl0-sys/dsh-large-proj-perf。社區目錄爲獨立站點,與 DeepSeek、幻方無官方從屬關係,收錄信息以目錄站爲準。