From 9111d8b66e013a881bae4e09646514be86ca4c87 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 4 May 2020 17:17:07 +1000 Subject: [PATCH 1/2] Fix the new capacity measurement in arenas. For the given code paths, the amount of space used in the previous chunk is irrelevant. (This will almost never make a difference to behaviour, but it makes the code clearer.) --- src/libarena/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libarena/lib.rs b/src/libarena/lib.rs index 0f0bd617f439c..b42a2e711acd9 100644 --- a/src/libarena/lib.rs +++ b/src/libarena/lib.rs @@ -224,7 +224,7 @@ impl TypedArena { new_capacity = last_chunk.storage.capacity(); loop { new_capacity = new_capacity.checked_mul(2).unwrap(); - if new_capacity >= currently_used_cap + n { + if new_capacity >= n { break; } } @@ -350,7 +350,7 @@ impl DroplessArena { new_capacity = last_chunk.storage.capacity(); loop { new_capacity = new_capacity.checked_mul(2).unwrap(); - if new_capacity >= used_bytes + needed_bytes { + if new_capacity >= needed_bytes { break; } } From 40d4868b3949c60de42e400baabe281a00a8c615 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 4 May 2020 19:25:09 +1000 Subject: [PATCH 2/2] Be less aggressive with `DroplessArena`/`TypedArena` growth. `DroplessArena` and `TypedArena` use an aggressive growth strategy: the first chunk is 4 KiB, the second is 8 KiB, and it keeps on doubling indefinitely. DHAT profiles show that sometimes this results in large chunks (e.g. 16-128 MiB) that are barely filled. Although these don't contribute to RSS, they clog up the DHAT profiles. This commit changes things so that the doubling stops at 2 MiB. This is large enough that chunk allocations are still rare (you might get 100s instead of 10s of them) but avoids lots of unused space in the worst case. It gives a slight speed-up to cycle counts in some cases. --- src/libarena/lib.rs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/libarena/lib.rs b/src/libarena/lib.rs index b42a2e711acd9..bbe80c26dcbf9 100644 --- a/src/libarena/lib.rs +++ b/src/libarena/lib.rs @@ -5,8 +5,7 @@ //! of individual objects while the arena itself is still alive. The benefit //! of an arena is very fast allocation; just a pointer bump. //! -//! This crate implements `TypedArena`, a simple arena that can only hold -//! objects of a single type. +//! This crate implements several kinds of arena. #![doc( html_root_url = "https://doc.rust-lang.org/nightly/", @@ -98,7 +97,13 @@ impl TypedArenaChunk { } } +// The arenas start with PAGE-sized chunks, and then each new chunk is twice as +// big as its predecessor, up until we reach HUGE_PAGE-sized chunks, whereupon +// we stop growing. This scales well, from arenas that are barely used up to +// arenas that are used for 100s of MiBs. Note also that the chosen sizes match +// the usual sizes of pages and huge pages on Linux. const PAGE: usize = 4096; +const HUGE_PAGE: usize = 2 * 1024 * 1024; impl Default for TypedArena { /// Creates a new `TypedArena`. @@ -211,6 +216,9 @@ impl TypedArena { #[cold] fn grow(&self, n: usize) { unsafe { + // We need the element size in to convert chunk sizes (ranging from + // PAGE to HUGE_PAGE bytes) to element counts. + let elem_size = cmp::max(1, mem::size_of::()); let mut chunks = self.chunks.borrow_mut(); let (chunk, mut new_capacity); if let Some(last_chunk) = chunks.last_mut() { @@ -221,18 +229,20 @@ impl TypedArena { self.end.set(last_chunk.end()); return; } else { + // If the previous chunk's capacity is less than HUGE_PAGE + // bytes, then this chunk will be least double the previous + // chunk's size. new_capacity = last_chunk.storage.capacity(); - loop { + if new_capacity < HUGE_PAGE / elem_size { new_capacity = new_capacity.checked_mul(2).unwrap(); - if new_capacity >= n { - break; - } } } } else { - let elem_size = cmp::max(1, mem::size_of::()); - new_capacity = cmp::max(n, PAGE / elem_size); + new_capacity = PAGE / elem_size; } + // Also ensure that this chunk can fit `n`. + new_capacity = cmp::max(n, new_capacity); + chunk = TypedArenaChunk::::new(new_capacity); self.ptr.set(chunk.start()); self.end.set(chunk.end()); @@ -347,17 +357,20 @@ impl DroplessArena { self.end.set(last_chunk.end()); return; } else { + // If the previous chunk's capacity is less than HUGE_PAGE + // bytes, then this chunk will be least double the previous + // chunk's size. new_capacity = last_chunk.storage.capacity(); - loop { + if new_capacity < HUGE_PAGE { new_capacity = new_capacity.checked_mul(2).unwrap(); - if new_capacity >= needed_bytes { - break; - } } } } else { - new_capacity = cmp::max(needed_bytes, PAGE); + new_capacity = PAGE; } + // Also ensure that this chunk can fit `needed_bytes`. + new_capacity = cmp::max(needed_bytes, new_capacity); + chunk = TypedArenaChunk::::new(new_capacity); self.ptr.set(chunk.start()); self.end.set(chunk.end());