st: add bulk ASCII fast path to twrite

Streaming text spent ~50% of its cycles in per-character tputc/tsetchar
dispatch. tputcrun() writes runs of plain printable ASCII directly into
the current line as whole cells, bailing out to tputc() whenever anything
non-trivial is in play (escape state, print/insert/sixel modes, GRAPHIC0
charset, pending wrap, active selection, non-ASCII bytes).

Cuts user time ~3x (0.79s -> 0.26s) and wall time ~45% when streaming
54MB of text. Verified pixel-identical rendering against the previous
build for escape-sequence torture tests, wide/CJK/emoji glyphs,
box-drawing, alt screen, scroll regions, insert/delete, resize reflow,
X primary selection contents and a syntax-highlighted vim session.
This commit is contained in:
a 2026-09-06 22:43:34 -05:00
parent 3a4e096d9b
commit 6fdc12966f
No known key found for this signature in database
GPG Key ID: 2F22877AA4DFDADB

86
st.c
View File

@ -3707,6 +3707,84 @@ check_control_code:
} }
} }
/*
* Print a run of plain printable ASCII characters using the current cursor
* attributes, writing whole cells directly into the current line. Returns the
* number of bytes consumed, or 0 when the fast path does not apply and the
* caller must fall back to tputc().
*/
static int
tputcrun(const char *buf, int buflen)
{
Glyph attr = term.c.attr;
Glyph *gp;
int i, run, avail;
/* Only handle the simple, overwhelmingly common case. */
if (term.esc || (term.mode & (MODE_PRINT|MODE_INSERT|MODE_SIXEL)) ||
term.trantbl[term.charset] == CS_GRAPHIC0 ||
(term.c.state & CURSOR_WRAPNEXT) ||
sel.mode != SEL_IDLE || sel.ob.x != -1)
return 0;
avail = term.col - term.c.x;
if (avail <= 0)
return 0;
/* Count leading printable ASCII bytes, bounded by the line width. */
for (run = 0; run < buflen && run < avail; run++) {
unsigned char c = (unsigned char)buf[run];
if (c < 0x20 || c >= 0x7f)
break;
}
if (run < 2)
return 0; /* not worth it; keep tputc's semantics */
attr.mode |= ATTR_SET;
attr.mode &= ~(ATTR_WIDE|ATTR_WDUMMY|ATTR_BOXDRAW|ATTR_KITTYPH);
#if OSC7_OSC8_PATCH
attr.link = curlink;
#endif // OSC7_OSC8_PATCH
#if KITTY_GRAPHICS_PATCH
attr.kitty = 0;
#endif // KITTY_GRAPHICS_PATCH
gp = &term.line[term.c.y][term.c.x];
/* A wide glyph may straddle the start of the run. */
if (gp->mode & ATTR_WDUMMY) {
if (term.c.x == 0)
return 0;
gp[-1].u = ' ';
gp[-1].mode &= ~ATTR_WIDE;
}
/* ...or the end of it. */
if (run < avail && (gp[run].mode & ATTR_WDUMMY)) {
gp[run].u = ' ';
gp[run].mode &= ~ATTR_WDUMMY;
}
for (i = 0; i < run; i++) {
gp[i] = attr;
gp[i].u = (unsigned char)buf[i];
}
term.dirty[term.c.y] = 1;
term.lastc = (unsigned char)buf[run-1];
if (term.c.x + run < term.col) {
term.c.x += run;
} else {
term.c.x = term.col - 1;
#if REFLOW_PATCH
term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = 1;
#endif // REFLOW_PATCH
term.c.state |= CURSOR_WRAPNEXT;
}
return run;
}
int int
twrite(const char *buf, int buflen, int show_ctrl) twrite(const char *buf, int buflen, int show_ctrl)
{ {
@ -3720,6 +3798,14 @@ twrite(const char *buf, int buflen, int show_ctrl)
#endif // SYNC_PATCH #endif // SYNC_PATCH
for (n = 0; n < buflen; n += charsize) { for (n = 0; n < buflen; n += charsize) {
/*
* Fast path: a run of plain printable ASCII printed with the
* current attributes onto the current line. This skips the
* per-character tputc/tsetchar dispatch, which dominates the
* profile while streaming large amounts of text.
*/
if (!show_ctrl && (charsize = tputcrun(buf + n, buflen - n)) > 0)
continue;
#if SIXEL_PATCH #if SIXEL_PATCH
if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) { if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) {
charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n); charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n);