Skip to content

High CPU usage (100%+) during LLM streaming in long sessions due to O(n) text buffer rendering #6172

Closed

Description

@anntnzrb

Description

OpenCode v1.0.201 exhibits sustained 100%+ CPU usage during active LLM streaming in long sessions, caused by an O(n) text buffer rendering loop in OpenTUI's native Zig renderer. The CPU drops to near-zero when idle, but memory accumulates and is not reclaimed even after session compaction. This makes large refactoring sessions extremely resource-intensive and can render the system sluggish.

Summary

Metric During Streaming When Idle
CPU 100-117% sustained 0.1-0.5%
State R+ (Running) S+ (Sleeping)
Physical Footprint 6.9 GB (peak 7.0 GB) Remains high
RSS 1.0-1.4 GB 1.2 GB (not reclaimed)

Root Cause

CPU samples show 100% of time spent in OpenTUI's text buffer renderer:

textBufferViewMeasureForDimensions
  → UnifiedTextBufferView.calculateVirtualLinesGeneric
    → rope.Rope.walkNode (recursive)
      → segment_callback
        → ArrayListAlignedUnmanaged.append
          → ArenaAllocator.alloc
            → PageAllocator.alloc
              → mmap (kernel)

This is in .7885f4ebfb6353d3-00000000.dylib - OpenTUI's native Zig renderer. The renderer appears to:

  1. Recalculate virtual lines for ALL content on every update (not just viewport)
  2. Allocate new memory via mmap for every calculation (not reusing allocations)
  3. During streaming, this triggers expensive O(n) recalculation for every token

Session Context

The session was performing a large codebase refactoring:

Metric Value
Session Title "Migrating from snowfall-lib to blueprint"
Files Modified 198
Lines Added 3,565
Lines Deleted 833
Session Parts 12,248
Session Messages 530+
Runtime 1h 15m+

Environment Details

System Information

Component Value
OS macOS 26.2 Tahoe (Build 25C56)
Kernel Darwin 25.2.0 (xnu-12377.61.12~1/RELEASE_ARM64_T8132)
Chip Apple M4
CPU Cores 10 (4 performance + 6 efficiency)
RAM 16 GB
Architecture arm64

OpenCode Environment

Component Value
OpenCode Version 1.0.201
Installation Method bunx opencode-ai@latest
Bun Version 1.3.2
Shell zsh
Terminal Multiplexer tmux 3.5a
Terminal Type tmux-256color

Active Language Servers

LSP PID CPU Memory
bash-language-server 35839 0% 2.2%
nixd 85540 0% 0.2%
pyright 86334 0% 0.3%

OpenCode Configuration

{
  "$schema": "https://opencode.ai/config.json",
  "tools": { "webfetch": false },
  "permission": {
    "edit": "allow",
    "webfetch": "allow",
    "bash": { "git push": "ask", "npm install": "ask", "*": "allow" }
  },
  "plugin": [
    "opencode-openai-codex-auth@4.2.0",
    "opencode-google-antigravity-auth"
  ],
  "mcp": {
    "exa": { "enabled": true, "type": "remote", "url": "https://mcp.exa.ai/mcp?..." },
    "deepwiki": { "enabled": true, "type": "remote", "url": "https://mcp.deepwiki.com/mcp" }
  },
  "autoupdate": false,
  "experimental": { "disable_paste_summary": true }
}
Process Statistics Over Time

During Active Agent Work

Timestamp CPU % MEM % RSS Physical Footprint Elapsed State
17:57:54 109.5 8.4 1.4 GB 5.5 GB 00:00:00 R+
18:04:13 102.2 7.4 1.2 GB 6.1 GB 01:04:01 R+
18:09:52 113.7 8.0 1.3 GB 6.7 GB 01:12:01 R+
18:12:37 100.9 6.3 1.0 GB 6.9 GB 01:14:48 R+

After Agent Completed (Idle)

Timestamp CPU % MEM % RSS Elapsed State
18:15:37 0.1 7.1 1.2 GB 01:17:49 S+

Key Observation: CPU dropped from ~110% to 0.1% once the agent finished, but memory (RSS 1.2 GB, 7.1% of 16GB) remained high and was not reclaimed.

Memory Map Analysis (vmmap)
Process:         opencode [85447]
Physical footprint:         6.7G
Physical footprint (peak):  6.8G

ReadOnly portion of Libraries: Total=925.1M resident=214.1M(23%)
Writable regions: Total=63.5G written=6.6G(10%) resident=1.1G(2%) swapped_out=5.7G(9%)

REGION TYPE                        SIZE     RESIDENT    DIRTY   SWAPPED   REGIONS
===========                     =======    ========    =====   =======   =======
VM_ALLOCATE                        6.1G      612.7M   612.7M      5.4G   167013
WebKit Malloc                     12.9G      354.4M   287.8M     57.4M       60
JS VM Gigacage                     1.5G       39.9M    11.6M      144K        7
JS VM Gigacage (reserved)         50.5G          0K       0K        0K        2
IOAccelerator                    640.2M       53.1M    53.1M    233.1M      426
JS JIT generated code            512.0M       15.7M    15.6M     8880K        3
__TEXT                           334.5M      191.9M       0K        0K      342
===========                     =======    ========    =====   =======   =======
TOTAL                             77.7G        1.4G     1.0G      5.7G   169429

Key observations:

  • VM_ALLOCATE: 6.1 GB virtual, 612.7 MB resident, 5.4 GB swapped - 167,013 regions
  • WebKit Malloc: 12.9 GB virtual - JSC heap
  • JS VM Gigacage: 52 GB reserved
CPU Sample Analysis (Full)

5-second sample showing the hot path:

Analysis of sampling opencode (pid 85447) every 1 millisecond
Process:         opencode [85447]
Date/Time:       2025-12-25 18:09:51.760 -0500
Launch Time:     2025-12-25 16:57:48.086 -0500
OS Version:      macOS 26.2 (25C56)
Physical footprint:         6.7G

Call graph:
    4193 Thread_5448600   DispatchQueue_1: com.apple.main-thread  (serial)
    + 4193 start  (in dyld)
    +   4193 ???  (in opencode)  [~15 frames of Bun internals]
    +     4193 ???  (in <unknown binary>)  [JIT code]
    +       4182 ???  (in <unknown binary>)
    +         4143 ???  (in <unknown binary>)
    +           4142 ???  (in <unknown binary>)
    +             3812 ???  (in <unknown binary>)
    +               3809 ???  (in <unknown binary>)
    +                 3767 ???  (in <unknown binary>)  [0x263aec94954]
    +                   3767 textBufferViewMeasureForDimensions + 256
    +                     3767 UnifiedTextBufferView.calculateVirtualLinesGeneric + 444
    +                       3767 rope.Rope.walkNode + 144
    +                         3767 rope.Rope.walkNode + 172
    +                           3767 walkLinesAndSegments.WalkContext.walker + 156
    +                             3767 calculateVirtualLinesGeneric.WrapContext.segment_callback + 624
    +                               3767 ArrayListAlignedUnmanaged.append + 356
    +                                 3767 ArenaAllocator.alloc + 120
    +                                   3767 PageAllocator.alloc + 136
    +                                     3767 mmap + 80
    +                                       3767 __mmap + 8

Hot Path Breakdown:

Function Library Samples %
__mmap libsystem_kernel.dylib 3767 89.8%
PageAllocator.alloc OpenTUI (.dylib) 3767 89.8%
ArenaAllocator.alloc OpenTUI (.dylib) 3767 89.8%
ArrayListAlignedUnmanaged.append OpenTUI (.dylib) 3767 89.8%
segment_callback OpenTUI (.dylib) 3767 89.8%
rope.Rope.walkNode OpenTUI (.dylib) 3767 89.8%
calculateVirtualLinesGeneric OpenTUI (.dylib) 3767 89.8%
textBufferViewMeasureForDimensions OpenTUI (.dylib) 3767 89.8%
Storage Analysis

Global OpenCode Storage

~/.local/share/opencode/
├── bin/           325 MB   (LSP servers)
├── log/           608 KB   (log files)
├── snapshot/      485 MB   (git snapshots)
├── storage/       385 MB   (session data)
│   ├── message/    81 MB   (531 directories)
│   ├── part/      283 MB   (12,248 directories)
│   ├── session_diff/ ~17 MB (498 files)

Current Session

{
  "id": "ses_4a87a33a1ffee9ZS0Sir0p7Akd",
  "version": "1.0.201",
  "title": "Migrating from snowfall-lib to blueprint",
  "summary": {
    "additions": 3565,
    "deletions": 833,
    "files": 198
  }
}
  • Session diff: 420 KB, 199 JSON entries, 1,394 lines
System Resource Impact

Memory Pressure

Swap I/O:
Swapins: 4470468 
Swapouts: 8129927 

Compressor Stats:
Pages used by compressor: 254459 
Pages decompressed: 78905151 
Pages compressed: 133605383 

System-wide memory free percentage: 60%

Swap Usage

vm.swapusage: total = 1024.00M  used = 156.88M  free = 867.12M  (encrypted)

Load Average

Load Avg: 2.46, 2.82, 2.95  (on 10-core system)
Related Issues

High CPU / Performance Issues

Issue Title Status
#5220 Glob search uses 100% of CPU OPEN
#4818 Heavy CPU Usage and making my M1 mac laggy after updating OPEN
#5130 Mac unresponsive, significant lag OPEN
#3822 Output starts to slow to a crawl OPEN

Memory Issues

Issue Title Status
#5363 opencode eating 70gb of memory? OPEN
#4315 Memory stays unbounded: ACP session map + compacted tool outputs OPEN
#3013 Uses a huge amount of memory OPEN
#6119 TUI fails to render with critical memory leak (72.5G VIRT) OPEN
#5700 Too high memory usage OPEN

TUI Freeze / Hang Issues

Issue Title Status
#731 Critical Stability Issues: App Freezing & Hanging Analysis OPEN
#5361 TUI freezes for ~10 seconds periodically on WSL2 OPEN
#4239 TUI hangs when shell outputs massive log OPEN
Related Pull Requests (Open)

These PRs appear to directly address the root cause:

PR Title Description
#3346 perf(tui/chat): virtualize message rendering to remove O(n) reflows Virtualizes chat blocks, caches per-block content, binary search for visible blocks
#2867 perf(tui): Eliminate O(n) scans from render hot path Local shimmer detection, header caching, virtual viewport rendering
#1493 fix: implement LRU cache for session memory management LRU eviction, TTL expiration, memory pressure handling
#4963 feat/perf: add pagination to modified files in sidebar Pagination for large file lists
#5432 fix(grep): stream ripgrep output to prevent memory exhaustion Stream instead of buffer

Key PR: #3346 - Virtualize Message Rendering

Changes:

  • Build per-block caches once, use binary search for visible blocks
  • Shimmer gating: Only animate when in-flight, re-render only when at bottom and backlog ≤ 2000 lines

Impact:

  • Streaming remains smooth on large histories
  • Rendering work scales with viewport, not backlog

Key PR: #2867 - Eliminate O(n) Scans

Optimizations:

  • Local shimmer detection - O(parts-in-message) instead of O(all-parts)
  • Virtual viewport rendering - O(1) scrolling regardless of message count
  • Fast selection updates - 10-50x speedup

Performance:

  • Before: ~3-10 FPS with 100+ messages
  • After: Smooth 60 FPS

Key PR: #1493 - LRU Cache for Session Memory

Features:

  • LRU eviction at capacity
  • TTL-based expiration
  • Memory pressure handling (aggressive cleanup >80%)

Impact:

  • 50-80% reduction in memory for long sessions
Technical Recommendations

For OpenTUI Text Buffer

  1. Cache virtual line calculations: Don't recalculate for unchanged content
  2. Virtualize to viewport: Only calculate virtual lines for visible rows
  3. Reuse allocations: Use object pooling instead of continuous mmap
  4. Batch updates during streaming: Coalesce rapid updates

For Session Memory Management

  1. Implement LRU eviction: As proposed in PR fix: implement LRU cache for session memory management #1493
  2. Actually clear tool outputs on compaction: Don't just mark as compacted
  3. Stream large outputs: Don't buffer entire tool outputs in memory

For Rendering Pipeline

  1. Implement virtualized scrolling: As proposed in PR perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #3346
  2. Cache rendered blocks: Don't re-render unchanged message blocks
  3. Throttle re-renders during streaming: Limit to 30-60 FPS

OpenCode version

1.0.201

Steps to reproduce

  1. Start OpenCode in a project directory:

    bunx opencode-ai@latest
  2. Begin a large refactoring task that:

    • Involves many files (100+ file modifications)
    • Generates substantial LLM output
    • Runs for an extended period (30+ minutes)
    • Uses tools that produce verbose output
  3. Monitor system resources:

    while true; do ps -p <PID> -o pid,%cpu,%mem,rss; sleep 5; done
  4. Observe:

    • CPU spikes to 100%+ during LLM streaming
    • Memory accumulates and doesn't decrease after compaction
    • System becomes sluggish

Screenshot and/or share link

Share link: https://opncd.ai/share/XmViOCAJ

Diagnostic data provided above instead of screenshots - includes:

  • Full CPU samples from macOS sample command
  • Memory maps from vmmap
  • Heap analysis from heap
  • Process statistics over time

Operating System

macOS 26.2 (Tahoe, Build 25C56) - Apple M4, 16GB RAM

Terminal

tmux 3.5a + zsh (TERM=tmux-256color)

Activity

  1. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    perfIndicates a performance issue or need for optimization
    on Dec 25, 2025
  2. github-actions commented on Dec 25, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  3. anntnzrb commented on Dec 25, 2025

    @anntnzrb
    ContributorAuthor

    New clues from current repo code (not seen in the issue text):

    1. The PRs referenced in the issue (perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #3346/perf(tui): Eliminate O(n) scans from render hot path for smooth animations #2867) appear to target the old Go TUI codepath (they mention files like packages/tui/internal/components/chat/messages.go). This repo/branch currently has no packages/tui or .go sources; the active TUI is TS/Solid + OpenTUI under packages/opencode/src/cli/cmd/tui/.... So those optimizations can’t be applied directly without re-implementing the ideas for OpenTUI.

    2. Potential client-side memory leak in the OpenTUI TUI store: packages/opencode/src/cli/cmd/tui/context/sync.tsx caps store.message[sessionID] to 100 entries (it shift()s old messages), but it does not delete the corresponding store.part[messageID] entries when messages are dropped. Over long sessions this can grow the parts map even while messages are bounded.

    3. "Compaction doesn't reclaim memory" has a concrete mechanism in current server code: packages/opencode/src/session/compaction.ts marks old tool parts with part.state.time.compacted but does not clear part.state.output. packages/opencode/src/session/message-v2.ts only swaps in "[Old tool result content cleared]" at model-message conversion time when time.compacted is set, so the original output still lives in storage and can still be loaded/materialized.

    4. Related to large-output pressure: packages/opencode/src/tool/grep.ts currently reads all ripgrep stdout into a string then splits + stats + sorts before truncating to 100. On huge result sets this is a worst-case memory/CPU spike and can indirectly worsen TUI rendering pressure during long sessions.

  4. berenar commented on Dec 26, 2025

    @berenar
    Contributor

    Yes, I encounter this issue frequently. I use GitHub Copilot as a provider, which charges for premium requests per session created. Therefore, I try to avoid creating new sessions and use /compact as much as possible instead. After a couple of hours in the same session, it becomes so slow that I need to Ctrl+C and open a fresh new session.
    Exiting OpenCode and reattaching to the same session doesn't help.

    My setup:

    • GitHub Copilot
    • Opus 4.5 / Sonnet 4.5
    • TUI
    • Tmux
    • Kitty
    • Apple M2 Pro, 16 gb, MacOS 26.1

    Update: yes I think this issue description makes sense with what I'm experiencing. During Plan, when it's very long (and the session is long too, I think), the response streaming keeps getting slower and slower, to the point it almost freezes OpenCode (the small animation too). And the output is only black and white, with no colors. so maybe it's an issue with the syntax highlighting 🕵️‍♂️. When switching to Build, it's not that laggy anymore (only when scrolling up in history)

    Image Image
  5. obito commented on Dec 27, 2025

    @obito
    Image Image

    Almost 13gb of RAM being consumed from a (very very) large session. I think I hold the record! It's very irresponsive, to the point where when it asks me for permission to run tools, I just hit enter and switch to something else until it register my keyboard entry (it takes around 5-10s).

    Sometimes the screen becomes black, then come back. Also, the response of the LLM is truncated or missing sometimes, before a tool call or at the end of the text in Plan mode.

  6. fglogan commented on Dec 30, 2025

    @fglogan

    I have 3 OC Instances running on MacOS Sonoma x86 older laptop with 16GB RAM - OC taking 13.2GB RAM and locking up computer.
    My project is in Rust but I looked into the issue a little deeper because I'm working on an edge device app and this is a no-go--but I have not touched zig so I'm wary..

    Took a look at this.
    --> The v1.0.200 scroll jank fix is different - that was TypeScript side. This is in the Zig renderer.

    calculateVirtualLinesGeneric
    → rope.Rope.walkNode
    → ArenaAllocator.alloc
    → mmap

    Every streaming token triggers a full rope walk. With 12k session parts that's O(n) work 30-60x per second.

    Two things need to happen:

    1. Viewport-aware traversal in OpenTUI.
      Right now calculateVirtualLinesGeneric walks from root. It should take viewport bounds and binary search to the start position, then stop when it passes the end. Changes O(n) to O(log n + visible rows).
    2. Arena reuseThe allocator is hitting mmap on every frame. Should reset the arena at frame start instead of allocating fresh. Kills the syscall overhead.
      The TypeScript side (block virtualization, shimmer gating) would help too but the real win is in the Zig layer. PR perf(tui/chat): virtualize message rendering to remove O(n) reflows during streaming #3346 had the right idea for the TS side but that was before the rewrite.
      Is there an OpenTUI repo where this should be filed separately, or is it vendored?
  7. kommander commented on Jan 5, 2026

    @kommander
    Collaborator

    The biggest memory hog should be solved in the latest version with the fixes around session filtering on the server and not loading all full sessions in the TUI. Some more text rendering performance fixes incoming with an opentui upgrade.

  8. sepsi77 commented on Jan 7, 2026

    @sepsi77

    I noticed that exiting opencode and then reopening the same session again seems to drop memory usage drastically. I went from 7GB RAM usage by OpenCode to 780MB. Just by /exit > reopen > /sessions > select the last session > continue.

  9. kommander commented on Jan 7, 2026

    @kommander
    Collaborator

    Does it still accumulate that much in the latest version during a session?

  10. sepsi77 commented on Jan 8, 2026

    @sepsi77
    Image

    @kommander yes, this is the current usage. I'm running version 1.1.4.

  11. kommander commented on Jan 8, 2026

    @kommander
    Collaborator

    Oh wow, I rarely cross 1gb over a day. Would you be able to share a session that causes so much memory usage so I can analyze? You can also send it to my GitHub email if you want to keep it private.

  12. sepsi77 commented on Jan 8, 2026

    @sepsi77

    @kommander so using /export and then email you the markdown file?

  13. sepsi77 commented on Jan 8, 2026

    @sepsi77

    @kommander here's the session export MD file.
    https://drive.google.com/file/d/14ofKYxz6D185aYX57G42yGXw06cO1ha4/view?usp=drive_link

    For some reason this starts at the middle of the last session. After my previous message (with screenshot showing 4.1 GB of memory usage) I existed, opened OC again and reloaded the last session. The export is a part of that. I'm not sure why it doesn't include my first message within that session. But perhaps it shows the usage pattern. Heavy tool use and running tests, linters (ruff) and type checking (basedpyright). Also uses sub-agents often.

    At the end of this relatively short session memory usage sits at 2.3 GB.

    My .local/share/opencode folder starts to get pretty big also. I'm wondering if this could contribute.

    seppo@LMA-VNF44C opencode % du -sh */
    353M bin/
    4.5M log/
    318M snapshot/
    1.5G storage/
    seppo@LMA-VNF44C opencode % cd storage
    seppo@LMA-VNF44C storage % du -sh */
    282M message/
    1.1G part/
    48K plugin/
    52K project/
    2.9M session/
    111M session_diff/
    8.0K session_share/
    492K todo/

  14. sepsi77 commented on Jan 8, 2026

    @sepsi77

    @kommander after nuking the storage folder memory usage seems to stay between 800 MB and 1 GB.

  15. mingodad commented on Jan 8, 2026

    @mingodad

    I'm having the same problem and after starting again after removing the '.local/share/opencode' folder I'm back to normal memory usage.
    Looking at the sessions in my case it seems that it was caused by the llm doing a test for debug using printf and it was filling the logs.

  16. berenar commented on Jan 8, 2026

    @berenar
    Contributor

    FYI I haven't had this issue for the past week, thank you so much for fixing it!

  17. kommander commented on Jan 8, 2026

    @kommander
    Collaborator

    Cool! That begs the question, are others that still have the issue using any plugins/MCPs or forks?

  18. shitastical commented on Jan 20, 2026

    @shitastical

    This is still an issue for me on linux.

  19. torrinworx commented on Feb 14, 2026

    @torrinworx

    Can this issue please get fixed? I'm experiencing this as well on the latest version, seeing all my cores maxed at 100% usage when idle. Not even streaming text from a model. The drops are when I switch to another window on my ubuntu desktop:

    Image
  20. xKei5150 commented on Feb 15, 2026

    @xKei5150

    I'm also experiencing this on long sessions. Almost no response from the models and just stuck loading. The /compact seems to work for now to mitigate the issue. The /handoff command also wont work once this happens

  21. torrinworx commented on Feb 18, 2026

    @torrinworx

    From a little bit of experimenting it seems like this is actually an issue with Vscode's terminal render loop. And it's not limited to OpenCode.

    OpenCode running with normal cpu usage in an Terminator terminal:
    Image

    Btop running in the vscode terminal also exhibits high cpu usage:

    Image

    I'm on:

    $ code --version
    1.109.2
    591199df409fbf59b4b52d5ad4ee0470152a9b31
    x64
    
  22. github-actions commented on Apr 20, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

opentuiThis relates to changes in v1.0, now that opencode uses opentuiperfIndicates a performance issue or need for optimization

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions