fix: 优化内存峰值与 CPU 性能,降低 100-300MB 内存占用

- claude.ts: 流式字符串拼接从 O(n²) += 改为数组累积 join,消除 4 处热点
- Messages.tsx: 合并 3 组独立遍历为单次 pass(thinking/bash 查找、3-filter 链、divider/selectedIdx)
- HighlightedCode.tsx: ColorFile 实例添加模块级 LRU 缓存(50 条),避免重复创建
- screen.ts: StylePool 衍生缓存添加 1000 条上限淘汰,防止无界增长
- CompanionSprite.tsx: TICK_MS 从 500ms 提升至 1000ms,减少 setState 频率
- connection.ts: MCP stderr 缓冲从 64MB 降至 8MB
- stringUtils.ts: MAX_STRING_LENGTH 从 32MB 降至 2MB
- sessionStorage.ts: Transcript 写入队列添加 1000 条上限
- query.ts: spread 改 concat 减少一次数组拷贝
- PromptInputFooterLeftSide.tsx: 显示进程 pid 便于调试

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-05-02 00:45:03 +08:00
parent f484fc34c8
commit ef10ad2839
13 changed files with 397 additions and 81 deletions

View File

@@ -1829,6 +1829,9 @@ async function* queryModel(
let ttftMs = 0
let partialMessage: BetaMessage | undefined
const contentBlocks: (BetaContentBlock | ConnectorTextBlock)[] = []
// Accumulate streaming deltas in arrays to avoid O(n²) string concatenation.
// Joined and assigned to contentBlock fields at content_block_stop.
const streamingDeltas = new Map<number, string[]>()
let usage: NonNullableUsage = EMPTY_USAGE
let costUSD = 0
let stopReason: BetaStopReason | null = null
@@ -2115,6 +2118,8 @@ async function* queryModel(
}
break
}
// Initialize delta accumulator for this content block
streamingDeltas.set(part.index, [])
break
case 'content_block_delta': {
const contentBlock = contentBlocks[part.index]
@@ -2144,8 +2149,9 @@ async function* queryModel(
})
throw new Error('Content block is not a connector_text block')
}
;(contentBlock as { connector_text: string }).connector_text +=
delta.connector_text
streamingDeltas
.get(part.index)
?.push(delta.connector_text as string)
} else {
switch (delta.type) {
case 'citations_delta':
@@ -2175,7 +2181,9 @@ async function* queryModel(
})
throw new Error('Content block input is not a string')
}
contentBlock.input += delta.partial_json
streamingDeltas
.get(part.index)
?.push(delta.partial_json as string)
break
case 'text_delta':
if (contentBlock.type !== 'text') {
@@ -2189,7 +2197,7 @@ async function* queryModel(
})
throw new Error('Content block is not a text block')
}
;(contentBlock as { text: string }).text += delta.text
streamingDeltas.get(part.index)?.push(delta.text!)
break
case 'signature_delta':
if (
@@ -2224,8 +2232,7 @@ async function* queryModel(
})
throw new Error('Content block is not a thinking block')
}
;(contentBlock as { thinking: string }).thinking +=
delta.thinking
streamingDeltas.get(part.index)?.push(delta.thinking!)
break
}
}
@@ -2257,6 +2264,32 @@ async function* queryModel(
})
throw new Error('Message not found')
}
// Join accumulated streaming deltas into the contentBlock fields
// to avoid O(n²) string concatenation during streaming.
const deltas = streamingDeltas.get(part.index)
if (deltas && deltas.length > 0) {
const joined = deltas.join('')
switch (contentBlock.type) {
case 'text':
;(contentBlock as { text: string }).text = joined
break
case 'thinking':
;(contentBlock as { thinking: string }).thinking = joined
break
case 'tool_use':
case 'server_tool_use':
contentBlock.input = joined
break
default:
if ((contentBlock.type as string) === 'connector_text') {
;(
contentBlock as { connector_text: string }
).connector_text = joined
}
break
}
streamingDeltas.delete(part.index)
}
const m: AssistantMessage = {
message: {
...partialMessage,