问题
汇总若干 HIGH 级渲染 / 启动 / 热路径性能问题(影响明确、修复成本低)。
1) 工具 schema 每次重建
getFunctionDeclarations() 每次 startChat / setTools 都遍历全部工具、{ ...tool.schema } 展开并递归跑 sanitizeParameters。
- 定义:
packages/core/src/tools/tool-registry.ts:499
- 调用:
packages/core/src/core/client.ts:404(setTools)与 :549(startChat,每次 resetChat 都触发)
30+ 个工具(含 MCP)一个会话会重建 100+ 次。建议:在 ToolRegistry 缓存声明数组,仅在工具增删(MCP 发现)时失效。
2) 100ms 轮询 plan mode
packages/cli/src/ui/App.tsx:741
const intervalId = setInterval(() => {
const currentPlanMode = config.getPlanModeActive();
if (currentPlanMode !== planModeActive) setPlanModeActive(currentPlanMode);
}, 100); // 每100ms检查一次
整个会话期间每秒 10 次。建议:改为在写入 plan mode 处发事件,useEffect 订阅,取消轮询。
3) 每次按键都检测 IDEA 环境
packages/cli/src/ui/App.tsx:1874 的 useInput 处理器内 detectIDEAEnvironment(),结果运行期不变;相同逻辑在 packages/cli/src/ui/utils/i18n.ts:15 还重复一遍。建议:模块初始化时算一次,导出常量。
4) tp() 每个参数都 new RegExp
packages/cli/src/ui/utils/i18n.ts:3427 与 packages/core/src/utils/simpleI18n.ts:83
text = text.replace(new RegExp(`\\{${paramName}\\}`, 'g'), String(value));
渲染期高频调用。建议:按参数名预编译并缓存到模块级 Map<string, RegExp>。
5) 压缩边界用 JSON.stringify 估长度
packages/core/src/services/compressionService.ts:69
const contentLengths = history.map((content) => JSON.stringify(content).length);
对全历史序列化只为算字节数。建议:在 recordHistory 追加时增量维护近似字符数,复用缓存值。
6) 流式 pending 列表用数组下标作 key
packages/cli/src/ui/App.tsx:2471 的 key={i} 使流式更新时 Ink/React 把每次重渲当作组件替换,HistoryItemDisplay 的 memo 失效。建议:用条目自身稳定标识(如工具组的 callId)作 key。
影响
这些都在「每按键 / 每渲染 / 每轮对话」级别反复发生,与 Ink 全树 reconciliation 的固有开销叠加,长会话体感明显。
本 issue 来自对 opensource 分支的一次代码审计(共 8 条,按严重程度拆分),结论均含 文件:行号 出处,欢迎指正。
问题
汇总若干 HIGH 级渲染 / 启动 / 热路径性能问题(影响明确、修复成本低)。
1) 工具 schema 每次重建
getFunctionDeclarations()每次startChat/setTools都遍历全部工具、{ ...tool.schema }展开并递归跑sanitizeParameters。packages/core/src/tools/tool-registry.ts:499packages/core/src/core/client.ts:404(setTools)与:549(startChat,每次 resetChat 都触发)30+ 个工具(含 MCP)一个会话会重建 100+ 次。建议:在
ToolRegistry缓存声明数组,仅在工具增删(MCP 发现)时失效。2) 100ms 轮询 plan mode
packages/cli/src/ui/App.tsx:741整个会话期间每秒 10 次。建议:改为在写入 plan mode 处发事件,
useEffect订阅,取消轮询。3) 每次按键都检测 IDEA 环境
packages/cli/src/ui/App.tsx:1874的useInput处理器内detectIDEAEnvironment(),结果运行期不变;相同逻辑在packages/cli/src/ui/utils/i18n.ts:15还重复一遍。建议:模块初始化时算一次,导出常量。4)
tp()每个参数都 new RegExppackages/cli/src/ui/utils/i18n.ts:3427与packages/core/src/utils/simpleI18n.ts:83渲染期高频调用。建议:按参数名预编译并缓存到模块级
Map<string, RegExp>。5) 压缩边界用 JSON.stringify 估长度
packages/core/src/services/compressionService.ts:69对全历史序列化只为算字节数。建议:在
recordHistory追加时增量维护近似字符数,复用缓存值。6) 流式 pending 列表用数组下标作 key
packages/cli/src/ui/App.tsx:2471的key={i}使流式更新时 Ink/React 把每次重渲当作组件替换,HistoryItemDisplay的 memo 失效。建议:用条目自身稳定标识(如工具组的callId)作 key。影响
这些都在「每按键 / 每渲染 / 每轮对话」级别反复发生,与 Ink 全树 reconciliation 的固有开销叠加,长会话体感明显。