From 169941cedac9656ff53074d8f8b0c07afecdcb41 Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sat, 3 Oct 2026 02:53:51 +0800 Subject: [PATCH] docs: record conversation store, timeline windowing and read worker results --- ...performance-experience-improvement-plan.md | 78 ++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/docs/roadmap/product-performance-experience-improvement-plan.md b/docs/roadmap/product-performance-experience-improvement-plan.md index 74129168..be089aa3 100644 --- a/docs/roadmap/product-performance-experience-improvement-plan.md +++ b/docs/roadmap/product-performance-experience-improvement-plan.md @@ -724,9 +724,85 @@ Renderer 主线程降速 4 倍(Main 不降速,每次 reload 后重新应用 175–258 ms);切换会话持平;流式场景两版方差都很大,未见一致变化。远程批量写库不在该基准 覆盖范围内,由单元测试和 `ipc.test.ts` 验证事务次数、顺序与回放去重。 +#### 2026-10-03 会话与消息领域(第一片) + +- **会话 store(`conversation-store.ts`、`conversation-selectors.ts`):** 会话数组移出 App state, + `getState()` 同步可读,取代 `conversationsRef` 及其 5 处直接写入;`set()` 先吸收流式 delta + 再同步更新。按会话 id 和列表两级通知,选择结果未变时保持同一对象。 +- **侧栏(`ConversationSidebar.tsx`):** 搜索匹配、5 秒重排、行级派生值移入侧栏自身订阅; + 切换会话时侧栏只重渲染状态翻转的两行。顺便修复 React 19.2 中 `memo` 组件内 + `useEffectEvent` 不刷新导致重排用旧列表的问题(有回归测试)。 +- **App 只订阅:** 活动会话、缓存面板的会话、数量,以及标题/审批/项目活动的按值比较 + 选择器。`App.tsx` 从 11,636 行降到约 10,960 行。 +- **仍在 App:** 未读、展开、菜单、重命名、删除等界面 ID;持久化、历史加载与合并; + 活动会话的更新(含 250 ms 流式吸收)仍重渲染 App。 + ### PERF-14 长列表虚拟化 -- **优先级 / 状态:** P2 / 待开始 +#### 2026-10-03 消息时间线窗口化 + +- **实现(`chat-message-window.ts`、`ChatHistoryPane.tsx`、`ChatTimeline.tsx`):** 自研窗口化, + 未引入依赖。只挂载视口附近的行(上限 60 行,外加获得焦点的行),其余用按实测高度或 + 平均高度估算的占位块代替;行高按会话缓存(最多 24 个会话)。以视口内的一行为锚点, + 行高变化或挂载/卸载时修正滚动位置,读者看到的内容不动。 +- **保留:** 96 px 贴底阈值和流式时每帧跟随;发送消息回到底部;“加载更早”仍按 80 条扩展 + 可滚动范围;笔记跳转先挂载目标再滚动;隐藏/显示和重新挂载恢复位置;隐藏面板不测量。 + 分帧补全(`09c415b`/`855b941`)已删除,打开面板一次提交有界行数。 +- **滚动滞后窗口:** 视口离开已挂载范围 160 px 内才重新取窗,取窗时挂载 1.5 屏余量,避免 + 每个滚动事件都重挂行。 +- **毛玻璃锚点:** 锚点以 `.chat-content` 的上内边距为基准,切换毛玻璃时阅读位置不变 + (由 `tests/frosted-glass.electron.test.ts` 在真实 Electron 中验证,该测试改为按消息 id 跟踪 + 锚点行)。 +- **已知限制:** 页内查找搜不到未挂载的消息;快速甩动时可能短暂看到空白占位;单条超长 + 消息仍整行挂载;估算高度会让滚动条滑块在首次经过时轻微变化。 + +### PERF-15/16 第一片:热点读路径移出 Main + +#### 2026-10-03 只读查询 worker + +- **实现(`readonly-query-reader.ts`、`readonly-query-worker.ts`):** 每个数据库文件一个只读 + worker 线程,沿用 `execution-stats-reader` 的模式,复用 `KnowledgeDatabase` / + `AssistantDatabase` 的生产方法,结果与同步路径同源。最多 32 个并发请求;worker 崩溃或 + 启动失败时转为同步执行,30 秒后再重启;`:memory:` 始终同步。`AbortSignal` 通过共享标志 + 取消(向量扫描每 256 行检查)。没有新增 `DatabaseSync` 白名单。 +- **已移出 Main:** 知识库检索(`retrieve`、`searchHybrid`);Story Graph 读取(Agent 通过 MCP + 调用,先在 SQL 中按范围预筛,超过 5 万个对象返回 `scan_limit_exceeded`);会话搜索(一条 + SQL + 注册函数,保持 `toLocaleLowerCase().includes()` 语义)。会话列表的 N+1 查询改为两次 + 批量查询,IPC 结构不变。 +- **`npm run perf:knowledge`(改用 Electron 运行):** + +| 文本块 | 混合检索时 Main 最大事件循环延迟(同步 → worker) | 单次耗时(同步 / worker) | +| --- | --- | --- | +| 5,000 | 54.9 → 19.5 ms | 47.7 / 53.3 ms | +| 20,000 | 204.3 → 19.6 ms | 188.6 / 198.9 ms | +| 50,000 | 489.9 → 18.6 ms | 466.8 / 478.6 ms | + +- **剩余:** 向量检索仍是全表扫描(只是不在 Main 上);写入和其余约 530 处同步调用仍在 + Main;独立数据进程和 `DatabaseSync` 白名单缩减未开始。 + +#### 2026-10-03 三项合并后的 `npm run perf:app` A/B(改动前为 `53b9ecb`) + +不降速:新版本通过全部 60 项阈值。4× 降速(改动前 3 轮,改动后 3 轮,滚动场景另取调整后 +2 轮): + +| 场景 | 改动前 | 改动后 | +| --- | --- | --- | +| 首次切换会话 p50 / p95 | 335–377 / 677–730 ms | 198–312 / 518–634 ms | +| 切回刚看过的会话 p95 | 379–386 ms | 179–240 ms | +| 切回最近的会话 p95 | 161–164 ms | 89–140 ms | +| 打开 2,000 条消息会话 | 448–484 ms,帧 p95 200–225 ms | 290–354 ms,帧 p95 8–9 ms | +| 滚动长会话:帧 p95 / 掉帧 | 17 ms / 7% | 58–67 ms / 15–16% | +| 流式输出、流式时打字 | 两版波动都很大,未见一致变化 | 同左 | +| 打字、空闲 | 持平 | 持平 | + +滚动是唯一变差的场景:改动前长会话一次性挂载所有已加载行,滚动只是移动画面;窗口化后 +每隔几屏要挂载新行(含 Markdown 渲染)并测量。初版每个滚动事件都重新取窗,掉帧约 70%; +加入滞后窗口后降到 15–16%。剩余开销主要在新行渲染和 `getBoundingClientRect` 测量,后续 +可把锚点测量合并到每帧一次,或给 Markdown 结果加缓存。 + +### PERF-14 状态 + +- **优先级 / 状态:** P2 / 进行中(消息时间线已窗口化;侧栏未虚拟化) - **实施条件:** `PERF-13` 完成会话与消息迁移,行组件 props 已稳定。 - **范围:** 消息时间线和会话侧栏优先;保留滚动位置、跳转、展开状态和“加载更早消息”语义。 替换新面板分两步渲染(`09c415b`);侧栏 5 秒重排状态随侧栏迁出 App。