diff --git a/src/terminal/PageList.zig b/src/terminal/PageList.zig index 6c4d892c1..39a48fb78 100644 --- a/src/terminal/PageList.zig +++ b/src/terminal/PageList.zig @@ -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(); }