mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-23 22:13:55 +00:00
termio: replace SegmentedPool with std.MemoryPool
The purpose of SegmentedPool was pointer-stable values for the pty write path, and the std.MemoryPool provides that. SegmentedPool is actually so old it predates a stdlib memory pool! Just noting why I did it in the first place. I also wrote it when I was pretty fucking bad at Zig, so I'm shocked its lasted this long. The write path is hot , so the replacement was benchmarked against the old SegmentedPool plus a rewrite simple Pool I did before realizing... wait... why not just a MemoryPool. Benchmarked using the real 240-byte xev write request. workload old std.MemoryPool depth-1 (keystroke echo) 3.93 ns/op 0.96 ns/op burst (1MiB paste, d=256) 4.27 ns/op 1.00 ns/op cold growth (32 -> 16k) 4.54 ns/op 6.48 ns/op malloc create/destroy 15.9 ns/op (baseline) Cold growth is slower but this is only a cost when the pool grows. Note this also gets rid of the preallocation, which didn't show any measurable performance benefit at all. This has the benefit of shrinking our ThreadData by ~10KB.
This commit is contained in:
@@ -5,7 +5,6 @@ const blocking_queue = @import("blocking_queue.zig");
|
||||
const cache_table = @import("cache_table.zig");
|
||||
const circ_buf = @import("circ_buf.zig");
|
||||
const intrusive_linked_list = @import("intrusive_linked_list.zig");
|
||||
const segmented_pool = @import("segmented_pool.zig");
|
||||
const split_tree = @import("split_tree.zig");
|
||||
|
||||
pub const BlockingQueue = blocking_queue.BlockingQueue;
|
||||
@@ -14,7 +13,6 @@ pub const CircBuf = circ_buf.CircBuf;
|
||||
pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList;
|
||||
pub const LimitedAllocator = @import("limited_allocator.zig").LimitedAllocator;
|
||||
pub const MessageData = @import("message_data.zig").MessageData;
|
||||
pub const SegmentedPool = segmented_pool.SegmentedPool;
|
||||
pub const SplitTree = split_tree.SplitTree;
|
||||
|
||||
test {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const SegmentedList = @import("segmented_list.zig").SegmentedList;
|
||||
const testing = std.testing;
|
||||
|
||||
/// A data structure where you can get stable (never copied) pointers to
|
||||
/// a type that automatically grows if necessary. The values can be "put back"
|
||||
/// but are expected to be put back IN ORDER.
|
||||
///
|
||||
/// This is implemented specifically for libuv write requests, since the
|
||||
/// write requests must have a stable pointer and are guaranteed to be processed
|
||||
/// in order for a single stream.
|
||||
///
|
||||
/// This is NOT thread safe.
|
||||
pub fn SegmentedPool(comptime T: type, comptime prealloc: usize) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
i: usize = 0,
|
||||
available: usize = prealloc,
|
||||
list: SegmentedList(T, prealloc) = .{ .len = prealloc },
|
||||
|
||||
pub fn deinit(self: *Self, alloc: Allocator) void {
|
||||
self.list.deinit(alloc);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Get the next available value out of the list. This will not
|
||||
/// grow the list.
|
||||
pub fn get(self: *Self) !*T {
|
||||
// Error to not have any
|
||||
if (self.available == 0) return error.OutOfValues;
|
||||
|
||||
// The index we grab is just i % len, so we wrap around to the front.
|
||||
const i = @mod(self.i, self.list.len);
|
||||
self.i +%= 1; // Wrapping addition to swe go back to 0
|
||||
self.available -= 1;
|
||||
return self.list.at(i);
|
||||
}
|
||||
|
||||
/// Get the next available value out of the list and grow the list
|
||||
/// if necessary.
|
||||
pub fn getGrow(self: *Self, alloc: Allocator) !*T {
|
||||
if (self.available == 0) try self.grow(alloc);
|
||||
return try self.get();
|
||||
}
|
||||
|
||||
fn grow(self: *Self, alloc: Allocator) !void {
|
||||
try self.list.growCapacity(alloc, self.list.len * 2);
|
||||
self.i = self.list.len;
|
||||
self.available = self.list.len;
|
||||
self.list.len *= 2;
|
||||
}
|
||||
|
||||
/// Put a value back. The value put back is expected to be the
|
||||
/// in order of get.
|
||||
pub fn put(self: *Self) void {
|
||||
self.available += 1;
|
||||
assert(self.available <= self.list.len);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "SegmentedPool" {
|
||||
var list: SegmentedPool(u8, 2) = .{};
|
||||
defer list.deinit(testing.allocator);
|
||||
try testing.expectEqual(@as(usize, 2), list.available);
|
||||
|
||||
// Get to capacity
|
||||
const v1 = try list.get();
|
||||
const v2 = try list.get();
|
||||
try testing.expect(v1 != v2);
|
||||
try testing.expectError(error.OutOfValues, list.get());
|
||||
|
||||
// Test writing for later
|
||||
v1.* = 42;
|
||||
|
||||
// Put a value back
|
||||
list.put();
|
||||
const temp = try list.get();
|
||||
try testing.expect(v1 == temp);
|
||||
try testing.expect(temp.* == 42);
|
||||
try testing.expectError(error.OutOfValues, list.get());
|
||||
|
||||
// Grow
|
||||
const v3 = try list.getGrow(testing.allocator);
|
||||
try testing.expect(v1 != v3 and v2 != v3);
|
||||
_ = try list.get();
|
||||
try testing.expectError(error.OutOfValues, list.get());
|
||||
|
||||
// Put a value back
|
||||
list.put();
|
||||
try testing.expect(v1 == try list.get());
|
||||
try testing.expectError(error.OutOfValues, list.get());
|
||||
}
|
||||
Reference in New Issue
Block a user