terminal: vectorize APC payload scanning (#13281)

Kitty graphics payloads are dispatched in bulk, but finding each slice
boundary still examines every byte with a scalar loop. This leaves large
direct base64 image transmissions parser-bound.

Scan ordinary APC bytes using the vector width recommended for the
compile target. Keep the scalar scan both as the tail and as the full
fallback when the target has no recommended vector width. Test
state-machine boundaries against byte-at-a-time parsing.

A ReleaseFast APC parser benchmark over the same 64 MiB Kitty graphics
corpus, with 10 warmups and 30 measured runs, produced:

```
                  mean       median
  scalar        37.6 ms      32.6 ms
  vectorized    22.3 ms      19.0 ms
```
Hyperfine reports the vectorized version as **1.69 times faster
overall**, with the median runtime improving by approximately 42
percent.

AI Disclosure: This patch was created with the assistance of GPT-5.6
This commit is contained in:
Mitchell Hashimoto
2026-07-10 08:11:30 -07:00
committed by GitHub

View File

@@ -793,6 +793,18 @@ pub fn Stream(comptime H: type) type {
assert(self.parser.state == .sos_pm_apc_string);
var end: usize = 0;
if (comptime std.simd.suggestVectorLength(u8)) |vector_len| {
const ByteVector = @Vector(vector_len, u8);
while (end + vector_len <= input.len) {
const bytes: ByteVector = input[end..][0..vector_len].*;
const invalid = (bytes == @as(ByteVector, @splat(0x18))) |
(bytes == @as(ByteVector, @splat(0x1A))) |
(bytes == @as(ByteVector, @splat(0x1B))) |
(bytes >= @as(ByteVector, @splat(0x80)));
if (@reduce(.Or, invalid)) break;
end += vector_len;
}
}
while (end < input.len) {
switch (input[end]) {
// Not apc_put bytes: CAN/SUB/ESC and most C1 exit
@@ -3899,11 +3911,14 @@ test "stream: apc aborted by CAN" {
// CAN (0x18) aborts the APC string via the anywhere => ground
// transition. Exiting the sos_pm_apc_string state emits apc_end,
// and the trailing bytes are printed, not treated as APC data.
s.nextSlice("\x1b_Gabc\x18def");
s.nextSlice("\x1b_Gabcdefghijklmnopqrstuvwxyz0123456789\x18def");
try testing.expectEqual(@as(usize, 1), s.handler.started);
try testing.expectEqual(@as(usize, 1), s.handler.ended);
try testing.expectEqualStrings("Gabc", s.handler.buf[0..s.handler.len]);
try testing.expectEqualStrings(
"Gabcdefghijklmnopqrstuvwxyz0123456789",
s.handler.buf[0..s.handler.len],
);
}
test "stream: apc scalar path matches" {
@@ -3917,3 +3932,29 @@ test "stream: apc scalar path matches" {
s.handler.buf[0..s.handler.len],
);
}
test "stream: apc vector boundaries match scalar path" {
const positions = [_]usize{ 15, 16, 17, 31, 32, 33, 63, 64, 65 };
const controls = [_]u8{ 0x18, 0x1A, 0x1B, 0x80, 0xFF };
for (positions) |position| for (controls) |control| {
var input: [96]u8 = undefined;
input[0..3].* = "\x1b_G".*;
@memset(input[3 .. 3 + position], 'a');
input[3 + position] = control;
input[4 + position] = '\\';
const bytes = input[0 .. 5 + position];
var bulk: Stream(ApcTestHandler) = .init(.{});
bulk.nextSlice(bytes);
var scalar: Stream(ApcTestHandler) = .init(.{});
for (bytes) |byte| scalar.next(byte);
try testing.expectEqual(scalar.handler.started, bulk.handler.started);
try testing.expectEqual(scalar.handler.ended, bulk.handler.ended);
try testing.expectEqualStrings(
scalar.handler.buf[0..scalar.handler.len],
bulk.handler.buf[0..bulk.handler.len],
);
};
}