This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch perf/final
in repository terminology.

View the commit online.

commit 36b326c8b7c6eb4f78e18ac4871bb6754898e469
Author: Cedric BAIL <[email protected]>
AuthorDate: Mon Aug 3 13:28:30 2026 -0600

    backlog: reuse a row's own allocation instead of reallocating each time
    
    termpty_save_new() freed its predecessor's cell block and calloc'd a fresh
    one for every scrolled line. On short-line output that was a quarter of the
    intake cost: termpty_save_new, termpty_save_free and the allocator underneath
    them.
    
    Keep the block the row already has whenever it is big enough. A ring slot is
    rewritten every time the backlog wraps, so its allocation settles at the
    longest line that slot has held and then stops churning. Termsave gains a
    'cap' field recording what the block holds, which is no longer the row's
    trimmed width; on LP64 it lands in padding the struct already had. That also
    lets termpty_save_expand() skip its realloc when the block is already big
    enough.
    
    Rows stay trimmed to their content. Sizing them all at screen width would
    make them interchangeable and poolable -- an Eina_Trash would fit -- but
    backlog memory would then scale with scrollback times columns rather than
    with what was printed, which at the maximum scrollback is hundreds of
    megabytes and is visible in the figure the options dialog reports.
    
    While here, skip the per-row link-refcount walk when this terminal has never
    created a link; hl.size stays zero until the first one and most never do.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 src/bin/backlog.c | 70 ++++++++++++++++++++++++++++++++++++++++++-------------
 src/bin/termpty.h |  3 +++
 2 files changed, 57 insertions(+), 16 deletions(-)

diff --git a/src/bin/backlog.c b/src/bin/backlog.c
index 6b687f49..482d5f67 100644
--- a/src/bin/backlog.c
+++ b/src/bin/backlog.c
@@ -55,16 +55,48 @@ termpty_save_extract(Termsave *ts)
    return ts;
 }
 
+/* Release the link refcounts held by a row's contents. hl.size stays zero
+ * until the first link is created, and most terminals never make one. */
+static void
+_ts_links_release(Termpty *ty, Termsave *ts)
+{
+   unsigned int i;
+
+   if (EINA_LIKELY(ty->hl.size == 0)) return;
+   for (i = 0; i < ts->w; i++)
+     {
+        if (EINA_UNLIKELY(ts->cells[i].att.link_id))
+          term_link_refcount_dec(ty, ts->cells[i].att.link_id, 1);
+     }
+}
+
 Termsave *
 termpty_save_new(Termpty *ty, Termsave *ts, int w)
 {
+   Termcell *cells;
+
+   /* Keep the block this row already holds when it is big enough. A ring slot
+    * settles at the longest line it has held and stops allocating. Rows stay
+    * trimmed to their content, so backlog memory does not scale with
+    * scrollback times columns. */
+   if (ts->cells && !ts->comp && (ts->cap >= (unsigned int)w))
+     {
+        _ts_links_release(ty, ts);
+        /* The caller decrements the link refcount of what it overwrites, so
+         * the cells must not still hold the previous row's link ids. */
+        if (w > 0) memset(ts->cells, 0, (size_t)w * sizeof(Termcell));
+        ts->w = w;
+        return ts;
+     }
+
    termpty_save_free(ty, ts);
 
-   Termcell *cells = calloc(1, w * sizeof(Termcell));
-   if (!cells ) return NULL;
+   cells = calloc(1, ((size_t)w + 1) * sizeof(Termcell));
+   if (!cells) return NULL;
+   _accounting_change((int64_t)w * sizeof(Termcell));
    ts->cells = cells;
    ts->w = w;
-   _accounting_change(w * sizeof(Termcell));
+   ts->cap = w;
    return ts;
 }
 
@@ -72,39 +104,45 @@ Termsave *
 termpty_save_expand(Termpty *ty, Termsave *ts, Termcell *cells, size_t delta)
 {
    Termcell *newcells;
+   size_t need = ts->w + delta;
 
-   newcells = realloc(ts->cells, (ts->w + delta) * sizeof(Termcell));
-   if (!newcells)
-     return NULL;
+   if (need > ts->cap)
+     {
+        newcells = realloc(ts->cells, need * sizeof(Termcell));
+        if (!newcells)
+          return NULL;
+        _accounting_change((-1) * (int64_t)(ts->cap * sizeof(Termcell)));
+        _accounting_change(need * sizeof(Termcell));
+        ts->cap = need;
+        ts->cells = newcells;
+     }
+   else
+     {
+        newcells = ts->cells;
+     }
 
    memset(newcells + ts->w,
           0, delta * sizeof(Termcell));
    TERMPTY_CELL_COPY(ty, cells, &newcells[ts->w], (int)delta);
 
-   _accounting_change((-1) * (int64_t)(ts->w * sizeof(Termcell)));
    ts->w += delta;
-   _accounting_change(ts->w * sizeof(Termcell));
-   ts->cells = newcells;
    return ts;
 }
 
 void
 termpty_save_free(Termpty *ty, Termsave *ts)
 {
-   unsigned int i;
    if (!ts) return;
    if (ts->comp) ts_comp--;
    else ts_uncomp--;
    ts_freeops++;
-   for (i = 0; i < ts->w; i++)
-     {
-        if (EINA_UNLIKELY(ts->cells[i].att.link_id))
-          term_link_refcount_dec(ty, ts->cells[i].att.link_id, 1);
-     }
+   _ts_links_release(ty, ts);
+
    free(ts->cells);
    ts->cells = NULL;
-   _accounting_change((-1) * (int64_t)(ts->w * sizeof(Termcell)));
+   _accounting_change((-1) * (int64_t)(ts->cap * sizeof(Termcell)));
    ts->w = 0;
+   ts->cap = 0;
 }
 
 void
diff --git a/src/bin/termpty.h b/src/bin/termpty.h
index 9f26de2e..d2ab89bf 100644
--- a/src/bin/termpty.h
+++ b/src/bin/termpty.h
@@ -245,6 +245,9 @@ struct tag_Termsave
    unsigned int   comp : 1;
    unsigned int   z    : 1;
    unsigned int   w    : 22;
+   /* Cells the allocation holds, which is >= 'w': a row keeps its block when
+    * rewritten. Free on LP64, where the pointer below already forced padding. */
+   unsigned int   cap;
    /* TODO: union ? */
    Termcell       *cells;
 };

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to