This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch master
in repository terminology.
View the commit online.
commit a046a47b36d8101f6e6ac656d2bb26afdea51495
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.
The memory figure the options dialog reports therefore changes meaning: it now
counts what is allocated -- each slot's high-water mark -- rather than the sum
of the trimmed widths. For the same scrollback it reads higher, and it is the
number that matches what the process actually holds.
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.
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 (1M context) <[email protected]>
---
src/bin/backlog.c | 74 +++++++++++++++++++++++++++++++++++++++++++------------
src/bin/termpty.h | 3 +++
2 files changed, 61 insertions(+), 16 deletions(-)
diff --git a/src/bin/backlog.c b/src/bin/backlog.c
index 6b687f49..cc529721 100644
--- a/src/bin/backlog.c
+++ b/src/bin/backlog.c
@@ -55,16 +55,52 @@ 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;
+ /* One cell more than 'cap' will advertise: termpty_line_length() returns 0
+ * for a blank row, and calloc(1, 0) may hand back NULL, which the check
+ * below would read as an allocation failure. The spare cell is deliberately
+ * left out of 'cap' and out of the accounting, so nothing can reach it. */
+ 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 +108,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 74313ff1..bee5cfdd 100644
--- a/src/bin/termpty.h
+++ b/src/bin/termpty.h
@@ -258,6 +258,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.