Skip to content

terminal: dispatch APC string bytes in bulk slices - #13270

Merged
mitchellh merged 1 commit into
ghostty-org:mainfrom
rockorager:apc-put-slice
Jul 10, 2026
Merged

mitchellh merged 1 commit into
ghostty-org:mainfrom
rockorager:apc-put-slice

Conversation

@rockorager

Copy link
Copy Markdown
Member

APC payloads such as Kitty graphics images can be megabytes of base64 data, but every byte was dispatched individually: through the VT state machine table, an apc_put action, the stream handler, the APC protocol handler, and finally a per-byte ArrayList append in the Kitty command parser. Five layers of dispatch per byte made large image transfers far slower than they needed to be.

Add a bulk fast path alongside the existing CSI fast paths in consumeUntilGround: scan the longest run of apc_put bytes (stopping at any byte the parse table doesn't treat as APC payload: CAN, SUB, ESC, and most C1 bytes exit or abort the string state, and 0xA0-0xFF are ignored by it) and dispatch the run as a single new apc_put_slice action. The APC handler identifies the protocol from the first few bytes as before, then passes the remainder of each slice to the protocol parser in bulk; the Kitty parser appends payload data with a single appendSlice. Ignored/unknown APC sequences now drop each slice in O(1) instead of per-byte dispatch.

The fast path is guarded the same way as the CSI fast paths: handlers with a vtRaw hook (the inspector) keep receiving per-byte apc_put actions, and the scalar next() path is unchanged.

Also add benchmark support: a ghostty-gen +kitty synthetic generator emitting well-formed Kitty graphics transmit commands with 4 KiB random base64 payloads (not valid image data; the corpus exercises the parsing paths, not image decoding), and a ghostty-bench +apc-parser benchmark that measures the stream -> APC -> Kitty parse path without image decode/storage.

Benchmarks on a 64 MiB corpus (hyperfine, ReleaseFast, x86_64 Linux, baseline is identical source with only the fast path disabled):

apc-parser: 1.061 s -> 43 ms (~25x)
terminal-stream (kitty): 1.163 s -> 72 ms (~16x)
terminal-stream (ascii): no change

The ascii case was verified with retired instruction counts (perf stat, pinned to one core) since wall time on the test machine has 4-7 ms of noise: 988,030,458 vs 988,045,833 instructions (+0.0016%), a fixed startup-size delta; the ground-state hot loop never reaches the new branch.

AI Disclosure: This code was written with the assistance of Fable 5 via Amp.

APC payloads such as Kitty graphics images can be megabytes of base64
data, but every byte was dispatched individually: through the VT state
machine table, an apc_put action, the stream handler, the APC protocol
handler, and finally a per-byte ArrayList append in the Kitty command
parser. Five layers of dispatch per byte made large image transfers
far slower than they needed to be.

Add a bulk fast path alongside the existing CSI fast paths in
consumeUntilGround: scan the longest run of apc_put bytes (stopping
at any byte the parse table doesn't treat as APC payload: CAN, SUB,
ESC, and most C1 bytes exit or abort the string state, and 0xA0-0xFF
are ignored by it) and dispatch the run as a single new apc_put_slice
action. The APC handler identifies the protocol from the first few
bytes as before, then passes the remainder of each slice to the
protocol parser in bulk; the Kitty parser appends payload data with a
single appendSlice. Ignored/unknown APC sequences now drop each slice
in O(1) instead of per-byte dispatch.

The fast path is guarded the same way as the CSI fast paths: handlers
with a vtRaw hook (the inspector) keep receiving per-byte apc_put
actions, and the scalar next() path is unchanged.

Also add benchmark support: a `ghostty-gen +kitty` synthetic generator
emitting well-formed Kitty graphics transmit commands with 4 KiB
random base64 payloads (not valid image data; the corpus exercises
the parsing paths, not image decoding), and a `ghostty-bench
+apc-parser` benchmark that measures the stream -> APC -> Kitty parse
path without image decode/storage.

Benchmarks on a 64 MiB corpus (hyperfine, ReleaseFast, x86_64 Linux,
baseline is identical source with only the fast path disabled):

  apc-parser:               1.061 s -> 43 ms  (~25x)
  terminal-stream (kitty):  1.163 s -> 72 ms  (~16x)
  terminal-stream (ascii):  no change

The ascii case was verified with retired instruction counts (perf
stat, pinned to one core) since wall time on the test machine has
4-7 ms of noise: 988,030,458 vs 988,045,833 instructions (+0.0016%),
a fixed startup-size delta; the ground-state hot loop never reaches
the new branch.
@rockorager
rockorager requested a review from a team as a code owner July 9, 2026 22:17
@ghostty-bot ghostty-bot Bot added the vt Control sequence related label Jul 9, 2026

@mitchellh mitchellh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to give this another look but this looks totally clean to me on first pass.

@mitchellh mitchellh added this to the 1.4.0 milestone Jul 10, 2026
@mitchellh
mitchellh merged commit 7f073c4 into ghostty-org:main Jul 10, 2026
96 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vt Control sequence related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants