mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-14 18:01:58 +00:00
terminal: PageList nodes are pooled individually from the gpa instead of an arena
This commit is contained in:
@@ -32,14 +32,17 @@ const log = std.log.scoped(.page_list);
|
||||
const native_freestanding = builtin.os.tag == .freestanding and
|
||||
!builtin.target.cpu.arch.isWasm();
|
||||
|
||||
/// The number of PageList.Nodes we preheat the pool with. A node is
|
||||
/// a very small struct so we can afford to preheat many, but the exact
|
||||
/// number is uncertain. Any number too large is wasting memory, any number
|
||||
/// too small will cause the pool to have to allocate more memory later.
|
||||
/// This should be set to some reasonable minimum that we expect a terminal
|
||||
/// window to scroll into quickly.
|
||||
/// The number of pages we preheat the page pool with. For operating systems
|
||||
/// that support it, pages are demand-paged (see PagePool) so this only
|
||||
/// costs us address space. For other operating systems, we don't preheat.
|
||||
const page_preheat = 4;
|
||||
|
||||
/// The number of nodes we preheat the node pool with. Unlike pages, nodes
|
||||
/// are ordinary heap memory so every idle preheated node costs real
|
||||
/// memory. A new PageList needs exactly one node for its first page, so
|
||||
/// we preheat that one and let the pool grow on demand.
|
||||
const node_preheat = 1;
|
||||
|
||||
/// The list of pages in the screen. These are expected to be in order
|
||||
/// where the first page is the topmost page (scrollback) and the last is
|
||||
/// the bottommost page (the current active page).
|
||||
@@ -299,7 +302,14 @@ const Node = struct {
|
||||
};
|
||||
|
||||
/// The memory pool we get page nodes from.
|
||||
const NodePool = std.heap.memory_pool.Managed(List.Node);
|
||||
///
|
||||
/// We don't use a std memory pool here because it is backed by an arena
|
||||
/// and we end up paying a lot of wasted memory for the growth factor when
|
||||
/// in practice we don't usually use many nodes.
|
||||
///
|
||||
/// We don't need the "untouched" property of our UntouchedPool but
|
||||
/// this gives us a GPA-allocated pool so we reuse it here.
|
||||
const NodePool = datastruct.UntouchedPool(List.Node, .of(List.Node));
|
||||
|
||||
/// The standard page capacity that we use as a starting point for
|
||||
/// all pages. This is chosen as a sane default that fits most terminal
|
||||
@@ -356,7 +366,7 @@ pub const MemoryPool = struct {
|
||||
page_alloc: Allocator,
|
||||
preheat: usize,
|
||||
) Allocator.Error!MemoryPool {
|
||||
var node_pool = try NodePool.initCapacity(gen_alloc, preheat);
|
||||
var node_pool = try NodePool.initCapacity(gen_alloc, gen_alloc, node_preheat);
|
||||
errdefer node_pool.deinit();
|
||||
var page_pool = try PagePool.initCapacity(gen_alloc, page_alloc, preheat);
|
||||
errdefer page_pool.deinit();
|
||||
@@ -899,18 +909,19 @@ fn verifyIntegrity(self: *const PageList) IntegrityError!void {
|
||||
|
||||
/// Release every page in the list during a teardown walk (deinit,
|
||||
/// reset, or an errdefer unwinding a partially built list): heap-owned
|
||||
/// pages go back to the page allocator and pool-owned pages back to the
|
||||
/// page pool. The nodes themselves are not touched; they live in the
|
||||
/// node pool.
|
||||
/// pages go back to the page allocator, pool-owned pages back to the
|
||||
/// page pool, and the nodes back to the node pool's free list.
|
||||
fn releasePages(pool: *MemoryPool, list: List) void {
|
||||
const page_alloc = pool.pages.allocator;
|
||||
var it = list.first;
|
||||
while (it) |node| : (it = node.next) {
|
||||
while (it) |node| {
|
||||
it = node.next;
|
||||
const page = node.restore(.discard);
|
||||
switch (node.owned) {
|
||||
.pool => releasePoolPage(pool, page),
|
||||
.heap => page_alloc.free(page.memory),
|
||||
}
|
||||
pool.nodes.destroy(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,8 +954,7 @@ pub fn deinit(self: *PageList) void {
|
||||
// Always deallocate our hashmap.
|
||||
self.tracked_pins.deinit(self.pool.alloc);
|
||||
|
||||
// Release every page. We don't need to deallocate the list or
|
||||
// nodes because they all reside in the pool.
|
||||
// Release every page and node back to the pools, then free the pools.
|
||||
releasePages(&self.pool, self.pages);
|
||||
self.pool.deinit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user