Orca 渲染进程 Agent 状态高频路径的性能优化:从 9,279 个监听者到单次发布的事务化折叠

发布时间:2026/9/6 21:24:29
Orca 渲染进程 Agent 状态高频路径的性能优化:从 9,279 个监听者到单次发布的事务化折叠 Orca 渲染进程 Agent 状态高频路径的性能优化从 9,279 个监听者到单次发布的事务化折叠【免费下载链接】orcaOrca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and VPS.项目地址: https://gitcode.com/GitHub_Trending/orca48/orca本文解析 Orca 针对渲染进程中高频 agent-status IPC 流量所采纳的设计方案为什么一条被展开的 100-worktree 谱系会让 Zustand 发布成本失控如何通过限定监听者预算、按事件顺序将突发折叠为单次 store 事务、以及配套的bench:idle-cpu基准工具链来复现与验证该回归。读完后你可以理解 Orca 中「burst 工作 ≈ 状态事件数 × 监听者数 × 选择器开销」这一结构放大器的成因并掌握如何在仓库中运行基准、核对监听者普查listener census以及用顺序/批量等价性测试保护语义不变量。背景为什么 agent 状态流量在渲染进程中变成性能问题Orca 可以在一条虚拟化的列表行里展示一个大型展开的 worktree 谱系。虚拟化只作用于根行并不作用于它的后代节点因此一个 100-worktree 的谱系会一次性挂载 100 个WorktreeCard实例。仓库中的相关卡片与谱系组件位于src/renderer/src/components/下如AgentStateDot.tsx、right-sidebar/FolderWorkspaceWorktreesPanel.tsx等侧边栏谱系的 DOM 结构由测试中以[data-worktree-sidebar] [data-worktree-id]选择器定位来保证。agent-status IPC 事件是突发性的bursty。渲染进程原本已将实时事件聚合到一个 33 ms 窗口内但最初的 flush 会对每个排队事件各执行一次独立的 Zustand 写入而 Zustand 对每次发布都会同步遍历所有监听者。因此单次突发的工作量随「事件数」和「已挂载订阅数」两个维度同时增长burst work ~ status events x store listeners x selector work生产 trace 显示渲染进程反复经由Set.forEach进入flushLiveAgentStatusBurst - applyAgentStatus - setAgentStatus - setState调用链一条确定性的 100-worktree fixture 可以稳定复现这个结构性放大器下文的main基线给出了当前实测的监听者数量与发布成本。后续有一次生产观察移除全部已配置远程主机后应用明显恢复。移除主机会根据主机类型和移除选项停止 relay/重连流量、移除已挂载的远程 worktree或两者兼有。该观察定位了生产触发源是「远程主机在场」但本身无法区分是流量体量还是已挂载监听者的扇出。一次只读的重连审计排除了「全量 PTY 回放导致状态重复发射」的系统性原因——回放字节会绕过 OSC 状态解析。重连仍会为每个已附加远程 pane 触发一次完整的终端缓冲重绘这是另一个独立的渲染工作量来源属于后续调查项。目标与非目标目标在密集 agent-status 流量下保持大型展开谱系仍然响应保留全部有序状态转移包括同一 burst 内同一 pane 的重复更新对一个延迟deferred实时突发只发布一次 agent-status 状态保持选择器标识与子组件渲染隔离让回归不依赖用户生产数据即可复现。非目标明确不做的事不改变 client/server 状态载荷或远程协议不按 pane 对状态事件做去重不改变 agent 新鲜度、保留、历史、标题、完成或 provider 会话行为不改变远程重连、PTY 回放或终端重绘行为不重新设计谱系呈现也不自动折叠 worktree。设计一限定已挂载订阅的扇出侧边栏组件不再按字段逐个注册监听者而是用浅比较选择内聚的状态 bundle。派生数组与 map 保留原有的浅标识行为因此无关的 store 写入不会导致卡片重渲染。完整 agent 列表模式保持其子级订阅边界紧凑模式直接传入已选择的行避免对同一输入做两次选择。确定性的 100-worktree fixture 将得到的监听者预算固定下来表面Surface监听者预算Worktree 卡片状态与缓存2Agent 行输入1Worktree 活动状态1关闭状态下的右键菜单1卸载测试要求监听者数量回到先前基线。在打包的原型中未播种 agent 的 fixture 从 8,518 个监听者降到 1,218带 100 个可见 agent 行时候选挂载 1,618 个。可对照「main 上的基线」一节中由基准工具直接报告的普查数字。设计二共享 working spinner 相位但不做逐元素动画查询working 行保留现有的合成器驱动 CSS 动画与共享视觉相位。每次挂载从文档时间线派生一个负的animation-delay而不是查询getAnimations()再去修改动画起始时间。这在不引入 JavaScript 动画时钟的前提下把逐行 Web Animations 设置从密集状态转移路径中移除。设计三按事件顺序折叠一个突发store 暴露单个更新动作与两种批量形式setAgentStatus(paneKey, payload, ...)保留面向即时实时路径的位置参数单更新 APIsetAgentStatuses(updates)应用一个预构建的有序列表transactAgentStatuses(operation)让 IPC 侧在单次 commit 前基于精确的暂存状态逐个推导更新。两个入口复用同一个单更新状态转移函数。批量 reducer 会把每个产生的状态传给下一个更新因此像working - waiting - done这样的序列保留与三次顺序调用完全一致的历史与时间戳。更新在折叠前绝不被按键索引或去重。实时 IPC 队列在处理前先执行 splice切出整队。这保留了既有的重入保证一个同步订阅者可以再入队新事件但不会让当前队列被递归排空。突发窗口外的第一个事件仍然立即应用在 33 ms 窗口内累积的事件作为单个有序事务应用。启动快照与有界 pending-hydration 重试走同一事务路径而不是为每个恢复的 pane 各发布一次。每个事务只构建一次 pane 路由归属语义与独立 resolver 的 first-match 一致。split-layout 叶子成员关系按每个 layout root 索引一次因此一个大型快照执行的是线性级的 tab 与叶子工作而不是为每个 pane 重扫所有已挂载 worktree。在仓库中这条实时路径已经落地src/renderer/src/hooks/ipc-events/agent-status-ipc-bridge.ts定义了LIVE_AGENT_STATUS_BURST_WINDOW_MS 33该文件第 21 行flushLiveAgentStatusBurst在发布前执行liveAgentStatusBurstQueue.splice(0)第 203–211 行其注释明确说明「同步 Zustand 订阅者可以入队下一个突发」的重入约束入队逻辑第 228–250 行保证只有真正applied的事件才占用 33 ms 窗口——被丢弃或 pending 的前导事件不会让后继事件支付突发延迟。批量入口transactAgentStatuses位于 store 切片src/renderer/src/store/slices/agent-status.ts顺序/批量等价性由src/renderer/src/store/slices/agent-status-batch.test.ts覆盖路由索引构建在src/renderer/src/hooks/ipc-events/agent-status-pane-routing-index.ts单事件应用逻辑在src/renderer/src/hooks/ipc-events/agent-status-event-applicator.ts。设计四事务之后再执行副作用需要已提交状态的生成标题工作被推迟到事务之后。被接受的更新还会请求新鲜度调度外层批量将这些请求合并在单次 commit 后只调度一次共享新鲜度定时器。生成标题请求按事件顺序折叠并一起发布包括首次写入与强制替换语义。解析后的 tab 标题在事务折叠期间被投影最终的标题变更再一起发布。由完成事件触发的 review 刷新保持为延迟微任务。批量标题应用保持事件顺序与重复 tab 行为同时只索引一次 owner、对每个变更的 owner 数组只克隆一次、对每个顶层 map 只替换一次使 commit 后的标题阶段对「已挂载 tab 数 变更标题数」呈线性。这个分离很重要在 Zustand updater 内部调用 store 动作会造成 store 重入而在 commit 前运行副作用则会让它观察到过期状态。agent-status-ipc-bridge.ts中的applyAgentStatusBatch第 164–195 行展示了这一模式事务内只折叠状态并收集notificationEffects与tabTitlesByTabId全部通过transaction.afterCommit在 commit 后统一执行。语义不变量顺序应用与批量应用必须在以下各点上保持一致实时 agent map 与保留 agent map状态历史、updatedAt、stateStartedAtagent 身份、模型、prompt、工具、助手消息与子 agent编排与 provider 会话连续性休眠会话与 launch-config 恢复记录退役/关闭 pane 的拒绝与继承状态抑制保留清理与 live-map 逐出agentStatusEpoch与sortEpoch跨中间转移的自动化完成观察生成标题输入、新鲜度调度与完成刷新。等价性测试使用固定时间戳并包含同一 pane 的重复转移。发布计数测试订阅真实 store要求非空批量恰好触发一次通知、空批量触发零次通知。基准契约基准启动一个 E2E 模式的 Electron 构建store 仅为测量而暴露。它创建一个含 100 个 worktree 的展开谱系验证 100 个已挂载卡片采集 store 监听者普查然后通过真实 store 动作应用播种的有序 agent-status 流量。基准测量的是同步 store 动作本身而不是实时 IPC 前导边缘或 commit 后的通知路径。一个真实 store 快照测试覆盖开启自动生成标题时 100 pane 的端到端预算一次状态发布、一次批量生成标题发布、一次批量解析标题发布关闭生成标题则去掉中间那次发布与 pane 数量无关。工件只记录比较所需的固定诊断字段请求与完成的批次数及更新数store 动作调用次数与观察到的发布次数耗时、吞吐量与调度漂移最终状态校验渲染进程平均、p95 与最大 CPU渲染进程定时器漂移与长任务已挂载卡片数与监听者数。原始进程清单、临时路径、pane 标识符和 DOM 文本仅作诊断用途不得嵌入可分享的报告。基线与候选必须在同一台机器、同一操作系统、同一 Electron 构建模式下运行。该约束内 macOS 与 Linux 的 CPU 采样可比Windows 的进程 CPU 采集目前无法支撑这一比较。基准工具链pnpm run bench:idle-cpu脚本入口在 package.json第 134 行bench:idle-cpu先确保 Electron 运行时再驱动 config/scripts/run-idle-cpu-benchmark.mjs后者组合四个模块模块职责config/scripts/idle-cpu-renderer-scale-fixture.mjs播种谱系、agent 行与侧边栏视图状态采集已挂载卡片与监听者普查config/scripts/idle-cpu-renderer-timing-probe.mjs页内定时器漂移与长任务探针运行 no-op 发布工作负载config/scripts/idle-cpu-process-sampling.mjs对 Electron 进程树分类并按角色采样 CPU/RSSconfig/scripts/idle-cpu-synthetic-spinners.mjs仅供测量的可见 spinner采样窗口会越过--sample-ms一直延伸到工作负载结算为止若工作负载越过保护阈值运行会直接失败而不是报告被截断的窗口——这就是为什么 2,000 次发布的运行报告的测量窗口比请求值更长。--zustand-publications通过真实 store 发布一个空的部分状态因此每次发布的成本恰好是一次完整的订阅者遍历没有任何额外内容。它把突发成本模型中listeners x selector work这一半与 agent-status 载荷工作隔离开来并且与 store API 无关——在批量切片落地前后测量的是同一件事。agent-status 写工作负载--agent-status-batches、--agent-status-write-mode不在该 harness 的当前参数集内它依赖setAgentStatuses随 store 切片一起落地。从 config/scripts/run-idle-cpu-benchmark.mjs 的参数解析第 35–143 行可以看到当前实际支持的参数与默认值--warmup-ms默认 15000、--sample-ms默认 30000、--interval-ms默认 1000下限 250、--worktrees默认 1、--lineage-depth需至少 2 个 worktree、--agents-per-worktree默认 0、--zustand-publications默认 0、--zustand-publication-interval-ms默认 100下限 1、--headful、--skip-build、--output、--disable-renderer-animations以及三个合成 spinner 参数。参数校验还强制发布跨度不得超过采样窗口第 135–141 行保证工作负载在窗口内完成。harness 的测量流程同样值得注意它用electron-vite build --mode e2e并注入VITE_EXPOSE_STOREtrue构建第 153–168 行等待window.__store就绪第 324 行为被测 Electron 实例设置隔离的HOME/USERPROFILE与独立的orca-data.json避免开发者真实 Codex 配置污染空闲测量第 296–318 行。main 上的基线在main的077f5a11cd4macOSarm6416 CPU上测量Electron 以electron-vite --mode e2e构建无头模式100 个 worktree、谱系深度 99、播种 100 个 agent 行10 s 预热、30 s 采样窗口。fixture 规模由普查确认而非假设100 个 store worktree、100 个已挂载卡片、100 个已挂载 agent 行、9,279 个 store 监听者。这个监听者数量正是本设计要压制的放大器。以 1 ms 节拍进行 2,000 次 no-op store 发布重复三次每次运行的监听者普查均为 9,279。指标中位数三次运行完成墙钟时间12,325.7 ms13,148.6 / 12,325.7 / 11,870.5p50 调度漂移5,150.3 ms5,432.3 / 5,150.3 / 4,945.2p95 调度漂移9,802.9 ms10,570.4 / 9,802.9 / 9,390.0渲染进程平均 CPU18.25%20.25 / 18.25 / 15.88渲染进程 p95 CPU32.59%40.07 / 32.59 / 31.28渲染进程定时器漂移 p957.0 ms7.3 / 5.3 / 7.02 s 内请求 2,000 次发布实际耗时约 12 s即在该规模下渲染进程只能维持约 160 次发布/秒。每次发布本身都很短——长任务观察器在三次运行中均记录到零条目——因此成本表现为调度漂移与持续 CPU而非离散的长任务。对比时应看漂移和 CPU而不是长任务计数。同规模下以--zustand-publications 0运行的空闲对照组报告渲染进程平均 CPU 6.63%、p95 17.11%、p95 定时器漂移 1.6 ms。因此约 11.6 个点的平均渲染 CPU 可归因于发布扇出而非已挂载 fixture 本身。两种情况都运行 200 个 spinner 动画对照组同时把动画成本排除在比较之外。复现命令pnpm run bench:idle-cpu -- --worktrees 100 --lineage-depth 99 \ --agents-per-worktree 1 --warmup-ms 10000 --sample-ms 30000 \ --zustand-publications 2000 --zustand-publication-interval-ms 1 \ --output /tmp/idle-cpu-baseline.json结果三次重复使用 100 个已挂载 worktree、谱系深度 99、100 个播种 agent 行并验证最终状态。来自重新生成证据集的中位数单次 2,000 更新突发顺序批量状态发布次数2,0001store 动作耗时3,692.0 ms188.7 ms更新吞吐541.7/s10,598.8/s渲染进程平均 CPU36.2%2.9%渲染进程 p95 CPU107.3%8.2%p95 长任务4,653 ms216 ms直接 store 事务将状态发布减少 99.95%store 动作耗时减少 94.9%处理速度提升 19.6 倍。渲染进程平均 CPU 下降 92.0%p95 CPU 下降 92.4%p95 长任务下降 95.4%。33 ms 节拍下 60 突发 × 32 更新的用例是一种持续饱和压力测试而非实时生产 SLO发布次数从 1,920 降到 60store 动作中位耗时从 2,791.9 ms 降到 323.3 ms完成时间中位数从 5,298.2 ms 降到 2,710.6 msp95 调度漂移从 3,073.0 ms 降到 664.9 ms长任务数从 57 降到 1。该节拍下渲染 p95 CPU 仍然饱和且噪声大因此不作为判别性指标。20 pane 的人工 OpenCode 回归通过按键回显中位数 12.4 ms、最差 25.2 ms最大定时器漂移 19.4 ms零丢弃渲染 backlog。这些数字来自打包原型在此作为目标值陈述store 切片落地后由 harness 重新测量。验收标准100-worktree fixture 保持在固定监听者预算之内延迟事务执行一次状态发布同时保留有序最终状态包括 live-map 在 500 行上限处的逐出100 pane 启动快照在关闭生成标题时执行一次状态发布与一次批量解析标题发布开启生成标题最多增加一次有序批量发布且保留最终状态与标题顺序/批量等价性测试在同 pane 转移与带副作用更新上通过候选重复运行中渲染 CPU 尾部与调度漂移改善20 pane 人工终端测试报告零丢弃输出 backlog 且无可察觉的键入延迟回归Web typecheck、聚焦单元测试、lint、max-lines ratchet 与 E2E 构建通过。兼容性与故障收敛本设计是渲染进程本地行为不新增 RPC 字段、流操作码、持久化数据、Git 命令或 provider 特定契约。Native、WSL、SSH、relay、folder workspace 与 git-worktree 的状态事件进入同一个渲染进程动作因此混合 client/server 版本不需要能力协商。故障收敛策略第一个实时事件保持立即应用启动回放与有界 pending 重试同步折叠、不等待 33 ms 实时突发窗口但把被接受的更新一起发布空批量是 no-op若某个更新过期或指向已退役的 authorityreducer 只跳过该更新并按顺序继续折叠后续事件。在源码中这条「pending 重试」路径同样有对应的防重入与 TTL 保护agent-status-ipc-bridge.ts中 pending 队列上限为 100 条MAX_PENDING_AGENT_STATUS_EVENTS、TTL 15 s、重试间隔 100 ms第 18–20 行flush 时若折叠抛错会把已切出的候选重新 unshift 回队首重试第 81–86 行避免一次异常丢弃整个突发、让其中所有 pane 停留在过期状态。小结Orca 的这条性能路径给出了一个可复用的方法论样本先用生产 trace 定位「事件数 × 监听者数 × 选择器开销」的结构性放大器再用确定性 fixture 监听者普查把回归固化为可复现资产随后按「限定监听预算 → 事件顺序折叠为单次事务 → commit 后执行副作用」的顺序改造最后用同一台机器上的基线/候选对照看调度漂移与 CPU 尾部而不是长任务计数来验收。设计文档见 docs/reference/renderer-agent-status-performance.md实现证据集中在src/renderer/src/store/slices/agent-status.ts、src/renderer/src/hooks/ipc-events/agent-status-ipc-bridge.ts及其配套测试中基准工具链在config/scripts/run-idle-cpu-benchmark.mjs与四个idle-cpu-*模块里均可直接在当前仓库中查看。【免费下载链接】orcaOrca is the ADE for working with a fleet of parallel agents. Run any coding agent with your own subscription. Available on desktop, mobile and VPS.项目地址: https://gitcode.com/GitHub_Trending/orca48/orca创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考