Skip to content

vt: reduce per-line allocation churn in Scrollback.Push #821

@nmelo

Description

@nmelo

Summary

vt.Scrollback.Push allocates a fresh slice for every line pushed via slices.Clone(line[:lastNonEmpty+1]).

In a profile from an app embedding x/vt for multiple panes, that per-push cloning accounts for roughly 71% of heap usage: about 97 MB with 11 panes active. DefaultScrollbackSize is already capped at 10,000, so the dominant cost here is not unbounded retention, but allocation churn on every push.

Current code

In scrollback.go:

func (s *Scrollback) Push(line uv.Line) {
    ...
    cloned := slices.Clone(line[:lastNonEmpty+1])
    ...
    s.lines = append(s.lines, cloned)
}

This means every scrolled line allocates a new backing array, even when line widths and write patterns are highly repetitive.

Why this matters

  • The allocation happens on every push, not just when the scrollback grows.
  • With several live panes, the heap cost becomes significant even at the default scrollback size.
  • The profile suggests this path is the dominant heap consumer in realistic multi-pane terminal workloads.

Possible improvements

  1. Reuse line buffers from a sync.Pool keyed by approximate line capacity.
  2. Introduce a copy-on-write strategy so unchanged/stable lines avoid immediate full clone costs.
  3. Store scrollback lines in a reusable slab/ring representation instead of one fresh slice per push.

I realize Push likely clones to avoid aliasing the render buffer, so some copy is necessary unless ownership changes. The request is mainly to reduce the per-push allocation pressure, not to eliminate safety.

Expected outcome

Anything that reduces fresh allocations in Scrollback.Push would help. Even a pooled-buffer approach behind the existing API could materially lower heap churn for applications with many concurrent emulators.

If useful, I can follow up with a benchmark or heap profile excerpt from the embedding app.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions