Commit 4c2924c

Eric Bower  ·  2026-06-19 23:07:19 -0400 EDT
parent 79169a0
perf: reduce allocations in the hot-path of the pty-proxy
3 files changed,  +17, -6
+5, -3
 1@@ -92,14 +92,16 @@ pub fn appendMessage(
 2     tag: Tag,
 3     data: []const u8,
 4 ) !void {
 5-    std.log.info("sending ipc message tag={s}", .{@tagName(tag)});
 6     const header = Header{
 7         .tag = tag,
 8         .len = @intCast(data.len),
 9     };
10-    try list.appendSlice(alloc, std.mem.asBytes(&header));
11+    // Guarantee capacity for header + payload in one check to avoid
12+    // intermediate realloc between the two appends on the hot path.
13+    try list.ensureTotalCapacity(alloc, list.items.len + @sizeOf(Header) + data.len);
14+    list.appendSliceAssumeCapacity(std.mem.asBytes(&header));
15     if (data.len > 0) {
16-        try list.appendSlice(alloc, data);
17+        list.appendSliceAssumeCapacity(data);
18     }
19 }
20 
+9, -2
 1@@ -2558,7 +2558,11 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
 2                 .read_buf = try ipc.SocketBuffer.init(daemon.alloc),
 3                 .write_buf = undefined,
 4             };
 5-            client.write_buf = try std.ArrayList(u8).initCapacity(client.alloc, 4096);
 6+            // 64KB initial capacity lets ~15 broadcast cycles (N_TTY_BUF_SIZE reads
 7+            // * header) accumulate before the first ArrayList growth. The write
 8+            // buffer is userspace-only: it drains via POLLOUT to the client socket,
 9+            // which has no corresponding kernel-imposed per-write limit.
10+            client.write_buf = try std.ArrayList(u8).initCapacity(client.alloc, 65536);
11             try daemon.clients.append(daemon.alloc, client);
12             std.log.info(
13                 "client connected fd={d} total={d}",
14@@ -2568,7 +2572,10 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
15 
16         const inp_flags = posix.POLL.IN | posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL;
17         if (poll_fds.items[1].revents & inp_flags != 0) {
18-            // Read from PTY
19+            // Read from PTY. Buffer is sized to N_TTY_BUF_SIZE (4096): the hard
20+            // kernel limit for the N_TTY line discipline. A larger buffer doesn't
21+            // help: each read() from a PTY master returns at most 4096 bytes
22+            // regardless of the userspace buffer size.
23             var buf: [4096]u8 = undefined;
24             const n_opt: ?usize = posix.read(pty_fd, &buf) catch |err| blk: {
25                 if (err == error.WouldBlock) break :blk null;
+3, -1
 1@@ -197,7 +197,9 @@ const OSC_133_A = "\x1b]133;A";
 2 /// makes the prompt invisible.
 3 /// See: https://github.com/neurosnap/zmx/issues/111
 4 pub fn rewritePromptRedraw(alloc: std.mem.Allocator, data: []const u8) ?[]const u8 {
 5-    // Quick scan: is there any OSC 133;A in this chunk?
 6+    // Fast-path: most PTY output has no escape sequences at all. A scalar
 7+    // byte scan for ESC is cheaper than the full string indexOf below.
 8+    if (std.mem.indexOfScalar(u8, data, '\x1b') == null) return null;
 9     if (std.mem.indexOf(u8, data, OSC_133_A) == null) return null;
10 
11     var result = std.ArrayList(u8).initCapacity(alloc, data.len + 200) catch return null;