Files
goodbuddy/docs/architecture/performance-principles.md
T
mesalogo 56ebd40bd8 feat: unify execution and add workspace file imports
Remove Ask/Execute modes and general product tool approvals so requests use the selected runtime, enabled capabilities, and execution account permissions. Migrate retired application metadata while preserving user content.

Retire the Results page while retaining conversation images and stored artifacts. Add local and remote workspace file imports, timestamps and sorting, improve image discovery and saving, and refine workspace navigation and supervision responsiveness. Include the accompanying web-search changes, tests, probes, and product documentation.

Remote imports require an updated Agent. Existing artifacts are not automatically copied into project directories. Validation evidence and outstanding failures remain documented in feature progress files; no full test suite or real-model calls were rerun for this commit.

Release note: Run requests without switching work modes, import files into a selected local or remote workspace folder, sort workspace files by date, and keep using conversation images after removal of the Results page.
2026-10-04 20:02:59 +08:00

10 KiB
Raw Blame History

GoodBuddy 性能原则

文档信息

  • 适用范围: Desktop(Electron Main、Preload、Renderer)及其后台进程和线程。
  • 用途: 新功能设计和代码评审的约束。多个 agent 并行修改代码时,以本文为统一标准。
  • 关联文档: docs/roadmap/product-performance-experience-improvement-plan.md(测量记录与实施进度)。
  • 参照: VS Code 的进程分工和渲染做法。我们没有在同一台机器上实测过 VS Code,下面的预算按常见的流畅标准设定。

1. 性能预算

“流畅”用下面的数字判断,不靠感觉。“标准机”指不降速运行 npm run perf:app,“低端机”指加上 GB_PERF_CPU_THROTTLE=4。

指标 标准机 低端机
按键到下一帧,p95 ≤ 16 ms ≤ 50 ms
点击到界面响应(切换会话、打开面板),p95 ≤ 100 ms ≤ 300 ms
稳定交互期间(打字、滚动、流式输出)超过 50 ms 的长任务 0 尽量为 0,总时长不超过场景时长的 5%
流式输出、滚动时的帧间隔,p95 ≤ 17 ms ≤ 33 ms
Main 事件循环单次阻塞 ≤ 16 ms —
Main 事件循环延迟,p99 ≤ 30 ms —
DOM 节点数 不随会话数、消息数增长 同左

标准机的阈值由 npm run perf:check 检查。低端机的数值目前只做记录,等结果稳定后再加进阈值检查。

2. 原则

P1 Main 只做调度

Main 负责窗口、生命周期、权限和消息转发。数据库查询、文档解析、哈希、向量计算、批量文件读写,都放到 worker 线程或 utilityProcess 里执行。

  • 新代码不能在 Main 里使用 DatabaseSync 和同步文件 API。lint 已有白名单,名单只能减少。
  • 暂时必须留在 Main 的同步操作,单次耗时要能证明在 16 ms 以内,并在代码里注明理由。
  • 耗时可能超过 16 ms 的操作,必须支持取消和进度上报,不能一直占着 Main。

P2 界面状态局部订阅

状态按领域放进外部 store,组件通过 selector 只订阅自己用到的那一部分。

  • 不再往 App.tsx 里加 useState、useEffect 或领域逻辑。App.tsx 有行数上限,只能往下调。
  • IPC 订阅放在 store 模块里,组件不直接调用 window.goodbuddy.*.on*。
  • 一个按键、一个流式片段,只能让显示它的那一小块界面重新渲染。
  • 传给 memo 组件的回调和 context 值,引用必须稳定。

模块约定(便于维护,所有拆分都按这个结构做):

层 文件命名 内容 不允许
Store <domain>-store.ts create<Domain>Store() 返回 getState / subscribe / 领域动作;纯 TS,不依赖 React;可单测 访问 DOM、调用 React hook
选择器 / Hook <domain>-selectors.ts 或 use-<domain>.ts 基于 useSyncExternalStore 的 selector,返回值引用稳定 在 selector 里新建对象却不做相等比较
同步层 <domain>-sync.ts 与 Main 的 IPC:首次加载、订阅推送、增量保存队列、失败重试、退出前刷盘 放在组件里;整份数据回写
视图 <Domain>*.tsx memo 组件,只从 selector 和稳定的动作读数据 接收 App 的大对象或内联回调
  • App.tsx 只负责布局外壳、路由和把 store 实例通过 context 提供给子树。新增领域状态一律走上表四层。
  • 每个 store 的单元测试和 store 文件放在一起(<domain>-store.test.ts),覆盖顺序、失败重试和边界情况。
  • Main 侧对应:<domain> 的数据库访问集中在一个 repository 类里,IPC handler 只做校验和转发。

P3 高频数据走专门通道,按帧合并

流式文本、终端输出、进度、滚动这类高频数据,不进全局状态。

  • 多条更新合并后,每帧最多提交一次;同步到全局状态的频率要更低,目前是 250 ms。
  • 高频路径上不能读取布局(getBoundingClientRect、scrollHeight 等),布局读取要集中到每帧一次。
  • 合并更新不能丢失、重复或打乱顺序。每条数据带序号,并有测试覆盖。

P4 数据只有一份来源,只传变化部分

  • 每类数据只有一个权威来源,由数据所在进程负责持久化。界面不把整份数据写回。
  • 跨进程的通知直接带上变化的内容,接收方不再“收到通知后重新查全量”。
  • 写入合并成批次和事务,不按每个事件单独开一个事务。

P5 工作量和可见内容成正比

  • 长列表只渲染可见部分,包括消息、会话、笔记、文件树和知识库列表。
  • 解析和渲染的结果要缓存,比如 Markdown 语法树。内容没变,就不重新计算。
  • 增量处理:只解析变化的部分。没有把握时,退回到慢但正确的完整处理,并记录在测试里。

P6 启动只做首屏需要的工作

  • 首屏之前只做显示应用壳和恢复上次界面所需的工作。
  • 其他初始化(Provider、Runtime、知识库、通知渠道等)按需执行,或等首屏显示后再空闲执行。
  • 后台模块在第一次使用时才加载。

P7 先测量,再修改

  • 先跑测量,拿到数据再改代码;每项改动都要有前后对比,基线用临时 worktree 构建。
  • 每个版本至少跑 3 轮,交替运行;结果差异小于同一版本多次运行之间的波动时,不能说成“有提升”。
  • 测量用隔离的用户目录和本地假模型,不发出外部模型请求,结果里不含用户内容。

P8 正确性优先于速度

  • 事件顺序、持久化、取消、崩溃恢复和显示结果都不能因为性能而改变。
  • 异步改造要先确认“先写入、后读取”的顺序,没有测试覆盖就不合并。
  • 风险高、收益小,或者会改变现有功能的优化先不做,在计划文档里记录理由。

P10 体验不倒退

性能优化只能让体验变好,不能用体验换速度。“行为不变”不仅指数据和结果,也包括用户能感知到的交互:

  • 动画和过渡保持原样。 平滑滚动(如“到底部”按钮)、展开收起、悬停、淡入淡出都不能因为优化而消失。任何瞬间写入 scrollTop/scrollTo 的代码都会打断正在进行的平滑滚动,必须在平滑滚动期间暂停。
  • 只渲染可见部分(P5)时,用户状态不能丢。 行被卸载后,它的局部状态也会消失。以下行必须保持挂载,或者把状态存到行外:有焦点的行、用户展开过 <details> 或输入过内容的行、选中文本的两端、待回答的问题和待审批项、打开菜单或正在重命名的行。
  • 只渲染可见部分时,基本操作要和完整列表一致: 跨行选择和复制、滚动位置不因上方插入或删除而跳动(关闭原生 overflow-anchor 时要自己做锚定)、快速拖动滚动条时没有空白帧、渲染结果缓存让重新挂载的行不出现“加载中”。
  • 合并和批处理不能增加交互延迟。 第一条数据立即处理(leading edge),只合并之后同一帧内的数据。打字回显、终端回显不能多等一帧。
  • 低频同步不能让用户看到旧数据。 复制、导出、保存这类读取全局状态的操作,要先把待同步的数据刷进去。
  • 体验要有测试。 涉及动画、滚动、焦点的改动,需要真实 Electron 测试(逐帧采样)或单元测试,并确认测试在旧代码上会失败。

P9 用工具守住规则

  • lint 规则和性能阈值是合并前的硬性条件,不靠人工记得。
  • 规则只能收紧,白名单和上限只能往下调。
  • 每个 agent 只提交自己改的文件和片段,不把别人未提交的改动带进自己的提交。

3. 评审清单

提交性能相关改动,或者会影响热点路径的功能改动时,逐项确认:

  1. Main 里有没有新增同步数据库调用、同步文件 IO,或者耗时可能超过 16 ms 的计算?
  2. 有没有往 App.tsx 里加状态?一次按键或一个流式片段,会让多大范围的界面重新渲染?
  3. 高频路径上有没有读取布局,有没有每次事件都提交状态?
  4. 有没有整份数据回写,或者“收到通知后重新查全量”?
  5. 列表的 DOM 数量会不会随数据量增长?
  6. 有没有在启动阶段新增同步初始化?
  7. 是否附上了 perf:app、perf:main 或 perf:knowledge 的前后对比?
  8. 顺序、取消、持久化和恢复,有没有测试覆盖?
  9. 动画、平滑滚动、过渡还在吗?有没有新增会打断平滑滚动的瞬间滚动写入?
  10. 只渲染可见部分的列表,滚走再滚回来,展开状态、输入内容、选区、焦点还在吗?
  11. 合并或延迟处理后,第一次响应(打字回显、点击反馈)有没有变慢?复制、导出读到的是不是屏幕上的最新内容?

4. 当前差距

截至 2026-10-04(第一期结项,见 docs/review/performance-phase-1-report-2026-10-04.md),按原则对照的主要差距:

原则 差距
P1 同步数据库调用仍在 Main,没有独立数据进程;但热点读已在 worker,高频写单次 0.1–0.3 ms,WAL checkpoint 在后台线程,启动恢复按索引读取。文档解析、分块和大文件哈希已移到 worker。消息附加数据冗余(子任务工具输出、tools 与 blocks 重复),大会话读取约 170 ms
P2 App.tsx 约 7,900 行。输入区、隐藏路由、任务和结果事件、切换会话已不再整体重渲染 App;当前会话的流式吸收(每 250 ms)、队列和活动记录仍经过 App
P3 已基本满足:流式输出和终端输出都已按帧合并
P4 活动记录已改为增量保存、按页读取(界面只保留已加载的页,计数和分组来自 Main 的汇总);部分 *Changed 通知之后还会重新查全量
P5 已基本满足:消息、会话、笔记、文件树、知识库文档表格都只渲染可见部分
P6 空配置启动到输入框可用约 0.96 秒;真实数据(2.4 GB)约 1.36 秒。本地工具环境已后台初始化,低频模块按需加载
P9 性能阈值还没有接入 CI,也没有真实低端机的基线

2026-10-04 补充:监督回顾的来源初始化和继续前版本校验已移入现有 Worker 入口的独立连接,扫描不持有写锁,清单按 200 条提交,生产失败不回退 Main 同步扫描。最终发布事务仍在 Main;实现与实测边界见监督者进度。