关于OpenCode CLI上下翻页的卡顿问题
- 内容介绍
- 文章标签
- 相关推荐
最近一直在用 OpenCode CLI 作为日常编码助手,整体体验还不错,但有个问题困扰我很
久了——对话稍微长一点,上下翻页就卡得要命。
我的配置是 9950X3D + 64GB DDR5
内存,按理说不应该卡成这样。所以花了点时间研究了一下,把发现分享给大家。
问题现象
- 新建会话时滚动很流畅
- 对话持续一段时间后(比如来回讨论十几轮),滚动开始明显掉帧
- 对话越长越卡,最后基本没法用鼠标滚轮翻页了
- 此时 CPU 占用会飙到 100%+
我用的是 Termius SSH
连服务器,一开始以为是网络问题或者终端模拟器的锅,后来发现换什么终端都一样卡。
排查过程
先去 GitHub 上搜了一圈,发现这是个已知问题,相关 issue 一大堆:
- High CPU usage (100%+) during LLM streaming in long sessions due to O(n) text buffer rendering · Issue #6172 · anomalyco/opencode · GitHub — 长会话 CPU
100%+,有人做了详细的性能分析 - Add configurable scrollback/message limit to reduce TUI lag · Issue #9930 · anomalyco/opencode · GitHub — 请求加个消息数量限制
- Long prompt history stalls the app, long load time · Issue #3746 · anomalyco/opencode · GitHub — 长对话历史导致卡顿
- Text rendering is VERY slow (and CPU usage is really high even when idle) · Issue #811 · anomalyco/opencode · GitHub — 文本渲染巨慢 + 空闲时 CPU
也高
看了 #6172 那个 issue,里面有人用 macOS 的 sample 命令抓了 CPU
热点,定位到了问题所在。
根本原因
OpenCode 用的是 OpenTUI(一个 Zig 写的终端 UI
框架)来渲染界面。问题出在它的文本缓冲区渲染逻辑:
每次滚动 → 重新计算全部内容的虚拟行布局 → O(n) 复杂度
也就是说,你对话历史有 1000 行,滚动一下就要遍历 1000 行重新算换行;有 10000
行就遍历 10000 行。对话越长,n 越大,越卡。
从 CPU 采样来看,热点调用栈是这样的:
textBufferViewMeasureForDimensions
→ calculateVirtualLinesGeneric
→ rope.Rope.walkNode (递归遍历)
→ segment_callback
→ ArrayListAlignedUnmanaged.append
→ ArenaAllocator.alloc
→ PageAllocator.alloc
→ mmap (系统调用)
每次滚动都在疯狂 mmap 分配内存,怪不得卡。
正确的解决思路
其实这类问题在 GUI/TUI
领域是有成熟方案的,核心思路是把布局计算和视图渲染解耦。
滚动本质上只是视口(Viewport)的位移,不应该触发文本重新排版。
策略一:布局缓存
- 只在窗口大小改变或有新内容时才重算布局
- 维护一个持久化的 VirtualLine 缓存列表
- 滚动时只改 offset,直接从缓存里取数据
优化前:scroll → layout() → draw()
优化后:scroll → offset += dy → draw_from_cache(offset)
策略二:视口裁剪(虚拟滚动)
就算有了缓存,如果 draw 还在遍历所有行判断"在不在屏幕里",依然会慢。
正确做法是只处理可见区域:
const start = scroll_y;
const end = @min(scroll_y + viewport_height, virtual_lines.len);
// 只遍历切片
for (virtual_lines[start..end]) |line| {
render_line(line);
}
策略三:增量布局(应对流式输出)
AI 在流式生成内容时,不要重算整个历史,只算新追加的部分。
优化前后对比
动作————————优化前————————————优化后
滚动触发————遍历全部文本,计算换行————直接读缓存索引
数据量——————处理 N 行(可能上万)————只取屏幕能显示的 ~50 行
复杂度——————————O(N)————————O(H),H 是屏幕高度
上游进展
好消息是开发者知道这个问题,已经有相关 PR 了:
- perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming by paulbettner · Pull Request #3346 · anomalyco/opencode · GitHub — 虚拟化消息渲染,移除 O(n)
reflow - perf(tui): Eliminate O(n) scans from render hot path for smooth animations by paulbettner · Pull Request #2867 · anomalyco/opencode · GitHub — 虚拟视口渲染,O(1) 滚动
坏消息是目前还没合入主分支,不知道什么时候能发版。
目前的临时缓解方案
在等上游修复的这段时间,能做的有限:
- 降低滚动速度 — 在 opencode.json 里加:
{
“tui”: {
“scroll_speed”: 1
}
} - 勤开新会话 — 对话别太长,感觉开始卡了就新建一个,这是目前最有效的规避方式
- 用 GPU 加速的终端 — 如果是本地用,Kitty / Alacritty / WezTerm 比 VSCode
内置终端或 Electron 类终端(如 Termius)渲染效率高 - 去 GitHub 给相关 issue 点个赞 — 让开发者知道这个问题影响了很多人
最后
写这篇主要是想整理一下思路,也给遇到同样问题的朋友一个参考。OpenCode
整体还是很好用的,就是这个性能问题确实影响体验。
如果有大佬愿意去贡献代码把那几个 PR 推进一下就更好了
有问题欢迎讨论
网友解答:--【壹】--:
我一直以为是对win不太友好 我在win上用 时间稍长就会这种情况
但是再mac上 没有明显的卡顿或者闪动 偶尔有一会
原来是这个问题 感谢大佬科普了
--【贰】--:
今天试一下,好卡,来 L 站果然搜到有人吐槽。claude 和 codex 在 autodl 好难用,配置代理麻烦。
--【叁】--:
不用 OpenCode
--【肆】--:
为啥技术贴没人回复?
--【伍】--:
不用是对的,一天崩溃n次,烦的不行了,issue多到爆
最近一直在用 OpenCode CLI 作为日常编码助手,整体体验还不错,但有个问题困扰我很
久了——对话稍微长一点,上下翻页就卡得要命。
我的配置是 9950X3D + 64GB DDR5
内存,按理说不应该卡成这样。所以花了点时间研究了一下,把发现分享给大家。
问题现象
- 新建会话时滚动很流畅
- 对话持续一段时间后(比如来回讨论十几轮),滚动开始明显掉帧
- 对话越长越卡,最后基本没法用鼠标滚轮翻页了
- 此时 CPU 占用会飙到 100%+
我用的是 Termius SSH
连服务器,一开始以为是网络问题或者终端模拟器的锅,后来发现换什么终端都一样卡。
排查过程
先去 GitHub 上搜了一圈,发现这是个已知问题,相关 issue 一大堆:
- High CPU usage (100%+) during LLM streaming in long sessions due to O(n) text buffer rendering · Issue #6172 · anomalyco/opencode · GitHub — 长会话 CPU
100%+,有人做了详细的性能分析 - Add configurable scrollback/message limit to reduce TUI lag · Issue #9930 · anomalyco/opencode · GitHub — 请求加个消息数量限制
- Long prompt history stalls the app, long load time · Issue #3746 · anomalyco/opencode · GitHub — 长对话历史导致卡顿
- Text rendering is VERY slow (and CPU usage is really high even when idle) · Issue #811 · anomalyco/opencode · GitHub — 文本渲染巨慢 + 空闲时 CPU
也高
看了 #6172 那个 issue,里面有人用 macOS 的 sample 命令抓了 CPU
热点,定位到了问题所在。
根本原因
OpenCode 用的是 OpenTUI(一个 Zig 写的终端 UI
框架)来渲染界面。问题出在它的文本缓冲区渲染逻辑:
每次滚动 → 重新计算全部内容的虚拟行布局 → O(n) 复杂度
也就是说,你对话历史有 1000 行,滚动一下就要遍历 1000 行重新算换行;有 10000
行就遍历 10000 行。对话越长,n 越大,越卡。
从 CPU 采样来看,热点调用栈是这样的:
textBufferViewMeasureForDimensions
→ calculateVirtualLinesGeneric
→ rope.Rope.walkNode (递归遍历)
→ segment_callback
→ ArrayListAlignedUnmanaged.append
→ ArenaAllocator.alloc
→ PageAllocator.alloc
→ mmap (系统调用)
每次滚动都在疯狂 mmap 分配内存,怪不得卡。
正确的解决思路
其实这类问题在 GUI/TUI
领域是有成熟方案的,核心思路是把布局计算和视图渲染解耦。
滚动本质上只是视口(Viewport)的位移,不应该触发文本重新排版。
策略一:布局缓存
- 只在窗口大小改变或有新内容时才重算布局
- 维护一个持久化的 VirtualLine 缓存列表
- 滚动时只改 offset,直接从缓存里取数据
优化前:scroll → layout() → draw()
优化后:scroll → offset += dy → draw_from_cache(offset)
策略二:视口裁剪(虚拟滚动)
就算有了缓存,如果 draw 还在遍历所有行判断"在不在屏幕里",依然会慢。
正确做法是只处理可见区域:
const start = scroll_y;
const end = @min(scroll_y + viewport_height, virtual_lines.len);
// 只遍历切片
for (virtual_lines[start..end]) |line| {
render_line(line);
}
策略三:增量布局(应对流式输出)
AI 在流式生成内容时,不要重算整个历史,只算新追加的部分。
优化前后对比
动作————————优化前————————————优化后
滚动触发————遍历全部文本,计算换行————直接读缓存索引
数据量——————处理 N 行(可能上万)————只取屏幕能显示的 ~50 行
复杂度——————————O(N)————————O(H),H 是屏幕高度
上游进展
好消息是开发者知道这个问题,已经有相关 PR 了:
- perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming by paulbettner · Pull Request #3346 · anomalyco/opencode · GitHub — 虚拟化消息渲染,移除 O(n)
reflow - perf(tui): Eliminate O(n) scans from render hot path for smooth animations by paulbettner · Pull Request #2867 · anomalyco/opencode · GitHub — 虚拟视口渲染,O(1) 滚动
坏消息是目前还没合入主分支,不知道什么时候能发版。
目前的临时缓解方案
在等上游修复的这段时间,能做的有限:
- 降低滚动速度 — 在 opencode.json 里加:
{
“tui”: {
“scroll_speed”: 1
}
} - 勤开新会话 — 对话别太长,感觉开始卡了就新建一个,这是目前最有效的规避方式
- 用 GPU 加速的终端 — 如果是本地用,Kitty / Alacritty / WezTerm 比 VSCode
内置终端或 Electron 类终端(如 Termius)渲染效率高 - 去 GitHub 给相关 issue 点个赞 — 让开发者知道这个问题影响了很多人
最后
写这篇主要是想整理一下思路,也给遇到同样问题的朋友一个参考。OpenCode
整体还是很好用的,就是这个性能问题确实影响体验。
如果有大佬愿意去贡献代码把那几个 PR 推进一下就更好了
有问题欢迎讨论
网友解答:--【壹】--:
我一直以为是对win不太友好 我在win上用 时间稍长就会这种情况
但是再mac上 没有明显的卡顿或者闪动 偶尔有一会
原来是这个问题 感谢大佬科普了
--【贰】--:
今天试一下,好卡,来 L 站果然搜到有人吐槽。claude 和 codex 在 autodl 好难用,配置代理麻烦。
--【叁】--:
不用 OpenCode
--【肆】--:
为啥技术贴没人回复?
--【伍】--:
不用是对的,一天崩溃n次,烦的不行了,issue多到爆

