Skip to content

Commit b2627b0

Browse files
authoredSep 22, 2016
Auto merge of #36592 - nnethercote:TypedArena, r=bluss
Lazily allocate TypedArena's first chunk Currently `TypedArena` allocates its first chunk, which is usually 4096 bytes, as soon as it is created. If no allocations are ever made from the arena then this allocation (and the corresponding deallocation) is wasted effort. This commit changes `TypedArena` so it doesn't allocate the first chunk until the first allocation is made. This change speeds up rustc by a non-trivial amount because rustc uses `TypedArena` heavily: compilation speed (producing debug builds) on several of the rustc-benchmarks increases by 1.02--1.06x. The change should never cause a slow-down because the hot `alloc` function is unchanged. It does increase the size of `TypedArena` by one `usize` field, however. The commit also fixes some out-of-date comments.
2 parents 6ad1084 + 80a4477 commit b2627b0

File tree

1 file changed

+55
-38
lines changed

1 file changed

+55
-38
lines changed
 

‎src/libarena/lib.rs

+55-38
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
//! of individual objects while the arena itself is still alive. The benefit
1616
//! of an arena is very fast allocation; just a pointer bump.
1717
//!
18-
//! This crate has two arenas implemented: `TypedArena`, which is a simpler
19-
//! arena but can only hold objects of a single type, and `Arena`, which is a
20-
//! more complex, slower arena which can hold objects of any type.
18+
//! This crate implements `TypedArena`, a simple arena that can only hold
19+
//! objects of a single type.
2120
2221
#![crate_name = "arena"]
2322
#![unstable(feature = "rustc_private", issue = "27812")]
@@ -51,16 +50,19 @@ use std::ptr;
5150
use alloc::heap;
5251
use alloc::raw_vec::RawVec;
5352

54-
/// A faster arena that can hold objects of only one type.
53+
/// An arena that can hold objects of only one type.
5554
pub struct TypedArena<T> {
55+
/// The capacity of the first chunk (once it is allocated).
56+
first_chunk_capacity: usize,
57+
5658
/// A pointer to the next object to be allocated.
5759
ptr: Cell<*mut T>,
5860

5961
/// A pointer to the end of the allocated area. When this pointer is
6062
/// reached, a new chunk is allocated.
6163
end: Cell<*mut T>,
6264

63-
/// A vector arena segments.
65+
/// A vector of arena chunks.
6466
chunks: RefCell<Vec<TypedArenaChunk<T>>>,
6567

6668
/// Marker indicating that dropping the arena causes its owned
@@ -69,7 +71,7 @@ pub struct TypedArena<T> {
6971
}
7072

7173
struct TypedArenaChunk<T> {
72-
/// Pointer to the next arena segment.
74+
/// The raw storage for the arena chunk.
7375
storage: RawVec<T>,
7476
}
7577

@@ -117,26 +119,26 @@ impl<T> TypedArenaChunk<T> {
117119
const PAGE: usize = 4096;
118120

119121
impl<T> TypedArena<T> {
120-
/// Creates a new `TypedArena` with preallocated space for many objects.
122+
/// Creates a new `TypedArena`.
121123
#[inline]
122124
pub fn new() -> TypedArena<T> {
123125
// Reserve at least one page.
124126
let elem_size = cmp::max(1, mem::size_of::<T>());
125127
TypedArena::with_capacity(PAGE / elem_size)
126128
}
127129

128-
/// Creates a new `TypedArena` with preallocated space for the given number of
129-
/// objects.
130+
/// Creates a new `TypedArena`. Each chunk used within the arena will have
131+
/// space for at least the given number of objects.
130132
#[inline]
131133
pub fn with_capacity(capacity: usize) -> TypedArena<T> {
132-
unsafe {
133-
let chunk = TypedArenaChunk::<T>::new(cmp::max(1, capacity));
134-
TypedArena {
135-
ptr: Cell::new(chunk.start()),
136-
end: Cell::new(chunk.end()),
137-
chunks: RefCell::new(vec![chunk]),
138-
_own: PhantomData,
139-
}
134+
TypedArena {
135+
first_chunk_capacity: cmp::max(1, capacity),
136+
// We set both `ptr` and `end` to 0 so that the first call to
137+
// alloc() will trigger a grow().
138+
ptr: Cell::new(0 as *mut T),
139+
end: Cell::new(0 as *mut T),
140+
chunks: RefCell::new(vec![]),
141+
_own: PhantomData,
140142
}
141143
}
142144

@@ -171,29 +173,37 @@ impl<T> TypedArena<T> {
171173
fn grow(&self) {
172174
unsafe {
173175
let mut chunks = self.chunks.borrow_mut();
174-
let prev_capacity = chunks.last().unwrap().storage.cap();
175-
let new_capacity = prev_capacity.checked_mul(2).unwrap();
176-
if chunks.last_mut().unwrap().storage.double_in_place() {
177-
self.end.set(chunks.last().unwrap().end());
176+
let (chunk, new_capacity);
177+
if let Some(last_chunk) = chunks.last_mut() {
178+
if last_chunk.storage.double_in_place() {
179+
self.end.set(last_chunk.end());
180+
return;
181+
} else {
182+
let prev_capacity = last_chunk.storage.cap();
183+
new_capacity = prev_capacity.checked_mul(2).unwrap();
184+
}
178185
} else {
179-
let chunk = TypedArenaChunk::<T>::new(new_capacity);
180-
self.ptr.set(chunk.start());
181-
self.end.set(chunk.end());
182-
chunks.push(chunk);
186+
new_capacity = self.first_chunk_capacity;
183187
}
188+
chunk = TypedArenaChunk::<T>::new(new_capacity);
189+
self.ptr.set(chunk.start());
190+
self.end.set(chunk.end());
191+
chunks.push(chunk);
184192
}
185193
}
186194
/// Clears the arena. Deallocates all but the longest chunk which may be reused.
187195
pub fn clear(&mut self) {
188196
unsafe {
189197
// Clear the last chunk, which is partially filled.
190198
let mut chunks_borrow = self.chunks.borrow_mut();
191-
let last_idx = chunks_borrow.len() - 1;
192-
self.clear_last_chunk(&mut chunks_borrow[last_idx]);
193-
// If `T` is ZST, code below has no effect.
194-
for mut chunk in chunks_borrow.drain(..last_idx) {
195-
let cap = chunk.storage.cap();
196-
chunk.destroy(cap);
199+
if let Some(mut last_chunk) = chunks_borrow.pop() {
200+
self.clear_last_chunk(&mut last_chunk);
201+
// If `T` is ZST, code below has no effect.
202+
for mut chunk in chunks_borrow.drain(..) {
203+
let cap = chunk.storage.cap();
204+
chunk.destroy(cap);
205+
}
206+
chunks_borrow.push(last_chunk);
197207
}
198208
}
199209
}
@@ -230,13 +240,14 @@ impl<T> Drop for TypedArena<T> {
230240
unsafe {
231241
// Determine how much was filled.
232242
let mut chunks_borrow = self.chunks.borrow_mut();
233-
let mut last_chunk = chunks_borrow.pop().unwrap();
234-
// Drop the contents of the last chunk.
235-
self.clear_last_chunk(&mut last_chunk);
236-
// The last chunk will be dropped. Destroy all other chunks.
237-
for chunk in chunks_borrow.iter_mut() {
238-
let cap = chunk.storage.cap();
239-
chunk.destroy(cap);
243+
if let Some(mut last_chunk) = chunks_borrow.pop() {
244+
// Drop the contents of the last chunk.
245+
self.clear_last_chunk(&mut last_chunk);
246+
// The last chunk will be dropped. Destroy all other chunks.
247+
for chunk in chunks_borrow.iter_mut() {
248+
let cap = chunk.storage.cap();
249+
chunk.destroy(cap);
250+
}
240251
}
241252
// RawVec handles deallocation of `last_chunk` and `self.chunks`.
242253
}
@@ -260,6 +271,12 @@ mod tests {
260271
z: i32,
261272
}
262273

274+
#[test]
275+
pub fn test_unused() {
276+
let arena: TypedArena<Point> = TypedArena::new();
277+
assert!(arena.chunks.borrow().is_empty());
278+
}
279+
263280
#[test]
264281
fn test_arena_alloc_nested() {
265282
struct Inner {

0 commit comments

Comments
 (0)