mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
terminal/snapshot: borrow fully buffered record payloads
When the record source already has the complete payload buffered — always the case for in-memory snapshots such as ghostty_snapshot_decoder_new_buf — the record reader now borrows the payload straight out of the source buffer instead of streaming it through the limited and hashing reader adapters. Payload decoders parse a fixed reader over the borrowed bytes, `finish` validates the CRC with a single bulk update, and the source advances only after validation. The page decoder takes a matching fast path: a fully buffered payload is parsed in place, skipping the staging allocation and copy it previously made per PAGE record. Streaming sources are unchanged. This is a modest win on its own; it is also the foundation for later commits whose buffered fast paths rely on the payload being contiguous. Benchmarks (see the first commit in this series for methodology; "prev" is the parent commit): | wasm | encode prev | encode | decode prev | decode | |-----------|------------:|---------:|------------:|---------:| | ascii | 3.43 ms | 3.50 ms | 2.81 ms | 2.68 ms | | styled | 3.14 ms | 3.15 ms | 7.12 ms | 7.04 ms | | truecolor | 5.33 ms | 5.27 ms | 12.11 ms | 12.04 ms | | cjk | 5.95 ms | 5.92 ms | 12.04 ms | 11.89 ms | | grapheme | 14.18 ms | 14.33 ms | 13.16 ms | 13.14 ms | | native | mode | prev | this | |--------|--------|--------:|--------:| | ascii | encode | 41.7 ms | 41.8 ms | | ascii | decode | 53.2 ms | 52.4 ms | | utf8 | encode | 47.2 ms | 47.4 ms | | utf8 | decode | 61.6 ms | 61.2 ms |
This commit is contained in:
@@ -233,7 +233,17 @@ pub const Decoder = struct {
|
||||
// payload, so this is the byte count of the tables and grid.
|
||||
const remaining = self.record_reader.header.payload_len - Header.len;
|
||||
|
||||
if (remaining <= max_staged_payload) {
|
||||
if (self.record_reader.payloadReader().bufferedLen() >= remaining) {
|
||||
// The complete payload is already buffered, e.g. borrowed from
|
||||
// an in-memory snapshot. Parse it in place with no staging
|
||||
// copy; `finish` still enforces the CRC and exact exhaustion.
|
||||
try decodePayloadBody(
|
||||
self.record_reader.payloadReader(),
|
||||
alloc,
|
||||
destination,
|
||||
self.header,
|
||||
);
|
||||
} else if (remaining <= max_staged_payload) {
|
||||
// Stage the payload with one bulk read. The whole payload passes
|
||||
// through the checksum hasher as one update and the payload
|
||||
// decoders then parse a flat buffer, which keeps per-row work free
|
||||
|
||||
@@ -257,6 +257,18 @@ pub const Reader = struct {
|
||||
limited: std.Io.Reader.Limited,
|
||||
hashing: std.Io.Reader.Hashed(Crc32c),
|
||||
|
||||
/// When the source already has the complete payload buffered, for
|
||||
/// example an in-memory snapshot, the payload is borrowed straight
|
||||
/// from the source buffer instead of streaming through the limited
|
||||
/// and hashing adapters. The checksum is then verified with one bulk
|
||||
/// update in `finish`, and the source is not advanced until `finish`.
|
||||
borrowed: ?Borrowed,
|
||||
|
||||
const Borrowed = struct {
|
||||
source: *std.Io.Reader,
|
||||
payload: std.Io.Reader,
|
||||
};
|
||||
|
||||
pub const InitError = Header.DecodeError;
|
||||
|
||||
/// Errors detected after a payload decoder returns.
|
||||
@@ -278,6 +290,21 @@ pub const Reader = struct {
|
||||
) InitError!void {
|
||||
self.* = undefined;
|
||||
self.header = try Header.decode(source);
|
||||
|
||||
// The complete payload is already sitting in the source buffer:
|
||||
// borrow it in place. Payload decoders read from a fixed reader
|
||||
// over the borrowed bytes and `finish` checksums them in one pass.
|
||||
if (source.bufferedLen() >= self.header.payload_len) {
|
||||
self.borrowed = .{
|
||||
.source = source,
|
||||
.payload = .fixed(
|
||||
source.buffered()[0..self.header.payload_len],
|
||||
),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
self.borrowed = null;
|
||||
self.limited = .init(
|
||||
source,
|
||||
.limited(self.header.payload_len),
|
||||
@@ -297,11 +324,34 @@ pub const Reader = struct {
|
||||
|
||||
/// Return the length-limited, checksum-updating payload reader.
|
||||
pub fn payloadReader(self: *Reader) *std.Io.Reader {
|
||||
if (self.borrowed) |*borrowed| return &borrowed.payload;
|
||||
return &self.hashing.reader;
|
||||
}
|
||||
|
||||
/// Require exact payload exhaustion and validate its CRC32C.
|
||||
pub fn finish(self: *Reader) FinishError!void {
|
||||
if (self.borrowed) |*borrowed| {
|
||||
if (borrowed.payload.bufferedLen() != 0) {
|
||||
return error.PayloadNotExhausted;
|
||||
}
|
||||
|
||||
var checksum: Checksum = .init(
|
||||
self.header.tag,
|
||||
self.header.payload_len,
|
||||
);
|
||||
checksum.writer().writeAll(
|
||||
borrowed.payload.buffer[0..borrowed.payload.end],
|
||||
) catch unreachable;
|
||||
if (checksum.final() != self.header.crc32c) {
|
||||
return error.InvalidChecksum;
|
||||
}
|
||||
|
||||
// The borrowed bytes validated, so consume them from the
|
||||
// source only now, leaving it positioned at the next record.
|
||||
borrowed.source.toss(self.header.payload_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.hashing.reader.bufferedLen() != 0 or
|
||||
self.limited.remaining != .nothing)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user