返回目录
部署运维 插件

dsh-render-perf

wendou-chen/dsh-render-perf

DSH Web 公式渲染性能治理:运行时注入公式渲染结果缓存(不改安装目录)+ 视口外渲染跳过。实测长会话切换卡顿 1832ms → 455ms(-75%)

Stars
1
Forks
0
Issues
0
更新
6 天前

PROJECT TOPICS

项目标签

INSTALL REFERENCE

安装参考

未验证
dsh plugin --profile web add github:wendou-chen/dsh-render-perf

该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。

PROJECT README

README

⚡ DSH 渲染性能治理 (dsh-render-perf)

License: MIT DSH Compatibility TypeScript Bundled with tsdown Cordis Microkernel

dsh-render-perf 是专为 DeepSeek Harness (DSH) Web 端打造的公式渲染性能治理插件

它解决一个非常具体的痛点:公式密集的长会话(考研数学、论文推导、竞赛题解)每次点击切换都要卡上十几甚至几十秒

插件在不修改 DSH 安装目录任何一个字节的前提下,运行时给前端数学渲染函数注入结果缓存,并把滚动视口外的消息块移出渲染流水线。


🔥 问题背景:不是浏览器卡,是每次都在重算

DSH Web 前端的 markdown 渲染器里,所有行内/块级公式都会调用同一个函数:

function ks(t,r){let i;try{i=G4.renderToString(t,{displayMode:r,throwOnError:!0})}
 catch(s){try{i=G4.renderToString(t,{displayMode:r,strict:"ignore",throwOnError:!1})}
 catch{return u.jsx("span",{className:"katex-error",...})}}
 return[...new DOMParser().parseFromString(i,"text/html").body.childNodes].map(C8)}

每次调用都要完整走一遍:

  1. KaTeX 编译:LaTeX 源码 → 语法树 → 布局 → HTML 字符串;
  2. DOMParser 解析:HTML 字符串 → 真实 DOM 子树;
  3. 递归转 React 元素:每一个 DOM 节点都变成独立的 React element 对象(单个公式往往 70+ 个节点)。

而这个函数没有任何缓存,也没有虚拟化——整个前端 bundle 里 content-visibility 出现 0 次。

于是每次切换会话,就是把这棵公式 DOM 树从零重建一遍。

实测基线(真实会话,Chrome 151)

会话「高数1000题多代理刷题解析」:解压后 3.26 MB 文本,含 1629 个块级公式 + 6484 个行内公式 = 8113 个 KaTeX 表达式

场景 DOM 元素 KaTeX 公式 主线程长任务
打开会话(首次) 27,291 352 2006 ms
切走再切回(第 2 次) 27,291 352 1832 ms
打开另一个较大会话 46,091 696 2330 + 1437 ms

第二次与第一次几乎同耗时——每次点击都在全量重建。视图完整展开上万条公式时,就是 20~40 秒级别的冻结,以及随之飙升的 GPU 显存占用。


✨ 核心特性矩阵

1. 🧠 公式渲染结果缓存(治本 · host 端)

  • 注册 webServer exact 路由拦截前端 JS 资产 /assets/index-<hash>.js 的响应,在响应流中为渲染函数注入缓存层;
  • 缓存键 = displayMode + LaTeX 源码跨会话共享,LRU 上限 20000 条;
  • 命中时 KaTeX 编译、DOMParser 解析、递归 React 元素构造三件事全部跳过
  • 资产名从 dist/index.html 实时解析,DSH 升级更换 hash 后自动适配。

2. 👁️ 视口外渲染跳过(降本 · 双端)

  • [data-chat-turn][data-chat-flow-key] 应用 content-visibility: auto + contain-intrinsic-size: auto 1200px
  • 滚动视口外的 turn 不参与样式计算、布局、绘制与合成,直接缓解 GPU 与显存压力;
  • contain-intrinsic-sizeauto 关键字记住上次实测尺寸,滚动条零跳动(实测 scrollTop 漂移 0 px)。

3. 📊 设置中心实时诊断面板(client 端)

设置 → 插件 → 搜索 render-perf 即可展开,实时显示:

  • 缓存命中次数 / 未命中次数(= 实际编译次数)/ 命中率
  • 缓存条目 / 上限
  • 视口外渲染跳过是否启用
  • 当前页面公式数与 DOM 节点数

判读方法:切换会话时 misses 不应增长,hits 持续增加即为缓存生效。也可在控制台直接读:

__DSH_RENDER_PERF__   // { hits, misses, max, mode, cache }

4. 🛡️ Fail-safe 设计(绝不帮倒忙)

  • 补丁采用结构化锚点匹配(函数名、参数名、结果变量、jsx 工厂名全部从原文捕获),对上游压缩变量名变化有较强容错;
  • 任何一步匹配失败 → 原样返回未修改的产物 + 日志告警,页面行为等同未安装;
  • 路由通过 ctx.effect 绑定插件 fiber,卸载即净
  • 提供自检脚本 npm run verify,随时确认当前 DSH 版本是否仍可注入。

5. 🔒 零侵入安装

  • 不修改 node_modules 里任何文件,只在 HTTP 响应流里做文章;
  • 不写 profile patch、不重启进程(配合超级注入器热加载);
  • 随插件卸载,一切回到原样。

📈 优化后实测

同一会话、同一测量口径(PerformanceObserver longtask):

场景 优化前 优化后 变化
切走再切回(热,公式全命中缓存) 1832 ms 455 ms −75%
打开会话(冷 / 部分命中) ~2000 ms 535 ms −73%
热切回时缓存 miss 增长 0 零重复编译
JS 堆占用 110 MB 72 MB 下降

按 DOM 元素归一化:每元素渲染成本 0.0671 ms → 0.0132 ms(约 5×)

正确性回归(全部通过)

检查项 结果
.katex / .katex-mathml / .katex-html 数量 与基线一致
MathML <annotation> 公式源码 完整保留
公式视觉渲染(积分号/根号/上下标/分数) 一致
控制台 React key 警告 / 新增错误
滚动到顶后 scrollTop 漂移 0 px
既有插件(公式点击复制 / render-guardian) 不受影响

📦 安装与使用指南

方式一:使用 DSH 超级注入器(推荐 · 免重启热加载)

若您的 DSH 已经装载了 dsh-super-injector,可直接在终端或 Agent 中一键免重启热注入:

# 1. 克隆仓库到本地目录
git clone https://github.com/wendou-chen/dsh-render-perf.git "D:/dsh-render-perf"
cd "D:/dsh-render-perf"

# 2. 安装依赖并编译产物(host + client 两端)
npm install
npm run build

# 3. 通过 DSH 工具执行热注入
dev_inject_plugin(dir: "D:/dsh-render-perf")

注入后硬刷新一次浏览器页面Ctrl+Shift+R),使新资产与样式生效。

方式二:手动配置 Profile 装配(冷启动方式)

  1. ~/.dsh/profiles/web/package.json(或 desktop Profile)的 dependencies 中添加软链接:

    {
      "dependencies": {
        "@dsh-external/dsh-render-perf": "link:D:/dsh-render-perf"
      },
      "dsh": {
        "bundles": [
          "@dsh-external/dsh-render-perf"
        ]
      }
    }
  2. 在对应 Profile 的 node_modules/ 下建立软链接(Windows Junction):

    cmd /c mklink /J "C:\Users\<YourUser>\.dsh\profiles\web\node_modules\@dsh-external\dsh-render-perf" "D:\dsh-render-perf"
  3. 启动或重启 DSH 服务即可生效。

卸载

dev_uninject_plugin(match: "dsh-render-perf")

卸载后刷新页面即回到原始未打补丁的前端。


⚙️ 配置项说明

字段 类型 默认 说明
enabled boolean true 总开关,关闭时插件不注册任何路由与样式
distDir string '' 手动指定 dsh-web-frontend/dist 目录;留空自动探测(进程入口推导 / 模块解析 / 常见全局路径)
mode 'element' \| 'html' element 补丁形态,见下

关于 html 模式(实测负优化,默认不启用)

html 模式把每个公式的子树交给 dangerouslySetInnerHTML,理论上省掉上百个 React 元素对象。实测反而更慢

模式 热切回主线程长任务
element(默认) 455 ~ 601 ms
html 699 ~ 774 ms

原因:数百次独立 innerHTML 赋值各自的解析与样式失效开销,超过了 React 批量建 DOM 的成本。该模式保留在代码中供不同 DSH 版本对比验证,默认不启用。


🔬 工作原理

补丁是怎么绕过安装目录进去的

@deepseek-ai/dsh-host-webserver 的路由匹配先查 exact 路由表、再走 fallback,而 @deepseek-ai/dsh-host-frontend-static 只占据 fallback seat。因此本插件注册一条 exact 路由:

GET /assets/index-<hash>.js
  → 读取原始产物(磁盘文件只读)
  → 应用补丁
  → 返回改写内容(cache-control: no-store,确保刷新即生效)

补丁做了什么

只「包裹」不「重写」——在渲染函数体首部插入缓存查询,把结尾的 return 改成「写缓存后返回」:

const __dshRPC = new Map(), __dshRPX = 20000, ...;

function ks(t, r) {
  const __dshRPK = (r ? 'D' : 'I') + t;          // 缓存键:displayMode + LaTeX
  const __dshRPH = __dshRPC.get(__dshRPK);
  if (__dshRPH !== void 0) { __dshRPS.hits++; return __dshRPH; }
  __dshRPS.misses++;

  /* ……原渲染逻辑与 KaTeX 错误分支原样保留…… */

  const __dshRPO = [...new DOMParser().parseFromString(i, "text/html").body.childNodes].map(C8);
  if (__dshRPC.size >= __dshRPX) __dshRPC.delete(__dshRPC.keys().next().value);
  __dshRPC.set(__dshRPK, __dshRPO);
  return __dshRPO;
}

为什么不做「让渲染进程常驻后台」

渲染进程本来就是常驻的;切换会话时销毁重建的是 React 组件树与 DOM,不是进程。所以正确方向是「别每次重算」+「视口外的别参与渲染」,而不是进程保活。


🧩 与 dsh-web-enhancements 的关系

完全独立、零耦合、可叠加使用,两者没有代码依赖:

dsh-web-enhancements dsh-render-perf
定位 UI / 交互增强套件 渲染性能治理
形态 client-only(DOM / Slot 注入) host + client(HTTP 响应改写 + 样式注入)
与 DSH 版本耦合度 低(走稳定 DOM 契约) (补丁锚定前端产物内部函数,需随版本适配)
迭代节奏 跟随功能需求 跟随 DSH 前端渲染实现变化

之所以独立成库而不并入增强套件:补丁与 DSH 前端实现强绑定,需要独立的版本适配节奏与 fail-safe 策略;而增强套件走的是稳定 DOM 契约,两者生命周期完全不同。合并会让前者的版本适配牵动后者的发布。

两者同时安装没有任何冲突;dsh-web-enhancements 的公式点击复制、render-guardian 等特性在补丁生效后工作正常(已回归验证)。


⚠️ 已知边界

  • 首次遇到的公式仍需编译一次:缓存是进程内内存缓存,页面刷新后清空。刷新后第一次打开某会话会付一次编译成本(实测 535 ms),之后来回切换接近零编译。

  • 跨会话切换仍要建 DOM:缓存消除的是「编译」成本;React 在新挂载时仍要为新 fiber 创建 DOM 节点(剩余约 455 ms)。要进一步消除,需要前端源码级的 keep-alive 或虚拟化。

  • 仅适用于 DSH Web 端:Electron 桌面端走 file:// 协议而非 webserver,路由拦截不生效,需要另做本地文件注入方案。

  • DSH 升级后:若上游改了渲染函数写法导致补丁失配,插件会告警并原样返回未打补丁的产物(页面正常,只是没有优化)。升级后建议跑一次自检:

    npm run verify                      # element 模式
    node scripts/verify-patch.mjs --mode=html
  • 本插件是运行时补丁,不是官方方案:上游若在 ks() 层实现缓存或改用虚拟滚动,本插件即可退役。如果你在用 DSH,欢迎顺手给上游提一个 issue。


📂 目录结构

dsh-render-perf/
├── LICENSE                   # MIT
├── CHANGELOG.md              # Keep a Changelog
├── README.md
├── package.json              # 模块元数据与 Peer 依赖声明
├── tsdown.config.ts          # 基于 Rolldown 的客户端打包配置 (window.__ModuleLoader__)
├── tsconfig.json             # TypeScript 编译配置 (ES2023 / NodeNext)
├── scripts/
│   └── verify-patch.mjs      # 补丁自检:对真实 bundle 应用补丁 + 幂等/语法校验
└── src/
    ├── index.ts              # Host 侧入口:dist 探测 + exact 路由 + Config 契约
    ├── patch.ts              # 补丁引擎:结构化锚点匹配 + element/html 双模式 + fail-safe
    ├── styles.ts             # 视口渲染跳过样式与 index.html 幂等注入
    └── client/
        └── index.ts          # Client 侧:设置中心「渲染性能」诊断面板

📄 License

MIT © 2026 wendou-chen

DSH (DeepSeek Harness) 是 DeepSeek 的开源项目。本插件为第三方社区增强,与 DeepSeek 官方无隶属关系。

CLASSIFICATION EVIDENCE

分类依据

项目类型插件
功能分类部署运维
规则置信度

系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: performance。