auto merge of #11358 : pcwalton/rust/typed-arenas, r=alexcrichton
A typed arena is a type of arena that can only allocate objects of one type. It is 3x faster than the existing arena and 13x faster than malloc on Mac. r? @brson
This commit is contained in:
commit
fda71f2630
2 changed files with 334 additions and 47 deletions
|
|
@ -7,8 +7,41 @@
|
|||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
//
|
||||
//! The arena, a fast but limited type of allocator.
|
||||
//!
|
||||
//! Arenas are a type of allocator that destroy the objects within, all at
|
||||
//! once, once the arena itself is destroyed. They do not support deallocation
|
||||
//! of individual objects while the arena itself is still alive. The benefit
|
||||
//! of an arena is very fast allocation; just a pointer bump.
|
||||
|
||||
// Dynamic arenas.
|
||||
#[allow(missing_doc)];
|
||||
|
||||
use list::{List, Cons, Nil};
|
||||
use list;
|
||||
|
||||
use std::at_vec;
|
||||
use std::cast::{transmute, transmute_mut, transmute_mut_region};
|
||||
use std::cast;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::num;
|
||||
use std::ptr;
|
||||
use std::mem;
|
||||
use std::rt::global_heap;
|
||||
use std::uint;
|
||||
use std::unstable::intrinsics::{TyDesc, get_tydesc};
|
||||
use std::unstable::intrinsics;
|
||||
use std::util;
|
||||
|
||||
// The way arena uses arrays is really deeply awful. The arrays are
|
||||
// allocated, and have capacities reserved, but the fill for the array
|
||||
// will always stay at 0.
|
||||
#[deriving(Clone)]
|
||||
struct Chunk {
|
||||
data: RefCell<@[u8]>,
|
||||
fill: Cell<uint>,
|
||||
is_pod: Cell<bool>,
|
||||
}
|
||||
|
||||
// Arenas are used to quickly allocate objects that share a
|
||||
// lifetime. The arena uses ~[u8] vectors as a backing store to
|
||||
|
|
@ -31,34 +64,6 @@
|
|||
// different chunks than objects without destructors. This reduces
|
||||
// overhead when initializing plain-old-data and means we don't need
|
||||
// to waste time running the destructors of POD.
|
||||
|
||||
#[allow(missing_doc)];
|
||||
|
||||
|
||||
use list::{List, Cons, Nil};
|
||||
use list;
|
||||
|
||||
use std::at_vec;
|
||||
use std::cast::{transmute, transmute_mut, transmute_mut_region};
|
||||
use std::cast;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::num;
|
||||
use std::ptr;
|
||||
use std::mem;
|
||||
use std::uint;
|
||||
use std::unstable::intrinsics;
|
||||
use std::unstable::intrinsics::{TyDesc, get_tydesc};
|
||||
|
||||
// The way arena uses arrays is really deeply awful. The arrays are
|
||||
// allocated, and have capacities reserved, but the fill for the array
|
||||
// will always stay at 0.
|
||||
#[deriving(Clone)]
|
||||
struct Chunk {
|
||||
data: RefCell<@[u8]>,
|
||||
fill: Cell<uint>,
|
||||
is_pod: Cell<bool>,
|
||||
}
|
||||
|
||||
#[no_freeze]
|
||||
pub struct Arena {
|
||||
// The head is separated out from the list as a unbenchmarked
|
||||
|
|
@ -110,8 +115,8 @@ impl Drop for Arena {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn round_up_to(base: uint, align: uint) -> uint {
|
||||
(base + (align - 1)) & !(align - 1)
|
||||
fn round_up(base: uint, align: uint) -> uint {
|
||||
(base.checked_add(&(align - 1))).unwrap() & !(&(align - 1))
|
||||
}
|
||||
|
||||
// Walk down a chunk, running the destructors for any objects stored
|
||||
|
|
@ -131,7 +136,7 @@ unsafe fn destroy_chunk(chunk: &Chunk) {
|
|||
|
||||
let after_tydesc = idx + mem::size_of::<*TyDesc>();
|
||||
|
||||
let start = round_up_to(after_tydesc, align);
|
||||
let start = round_up(after_tydesc, align);
|
||||
|
||||
//debug!("freeing object: idx = {}, size = {}, align = {}, done = {}",
|
||||
// start, size, align, is_done);
|
||||
|
|
@ -140,7 +145,7 @@ unsafe fn destroy_chunk(chunk: &Chunk) {
|
|||
}
|
||||
|
||||
// Find where the next tydesc lives
|
||||
idx = round_up_to(start + size, mem::pref_align_of::<*TyDesc>());
|
||||
idx = round_up(start + size, mem::pref_align_of::<*TyDesc>());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +180,7 @@ impl Arena {
|
|||
fn alloc_pod_inner(&mut self, n_bytes: uint, align: uint) -> *u8 {
|
||||
unsafe {
|
||||
let this = transmute_mut_region(self);
|
||||
let start = round_up_to(this.pod_head.fill.get(), align);
|
||||
let start = round_up(this.pod_head.fill.get(), align);
|
||||
let end = start + n_bytes;
|
||||
if end > at_vec::capacity(this.pod_head.data.get()) {
|
||||
return this.alloc_pod_grow(n_bytes, align);
|
||||
|
|
@ -227,7 +232,7 @@ impl Arena {
|
|||
|
||||
tydesc_start = head.fill.get();
|
||||
after_tydesc = head.fill.get() + mem::size_of::<*TyDesc>();
|
||||
start = round_up_to(after_tydesc, align);
|
||||
start = round_up(after_tydesc, align);
|
||||
end = start + n_bytes;
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +241,7 @@ impl Arena {
|
|||
}
|
||||
|
||||
let head = transmute_mut_region(&mut self.head);
|
||||
head.fill.set(round_up_to(end, mem::pref_align_of::<*TyDesc>()));
|
||||
head.fill.set(round_up(end, mem::pref_align_of::<*TyDesc>()));
|
||||
|
||||
//debug!("idx = {}, size = {}, align = {}, fill = {}",
|
||||
// start, n_bytes, align, head.fill);
|
||||
|
|
@ -314,3 +319,284 @@ fn test_arena_destructors_fail() {
|
|||
fail!();
|
||||
});
|
||||
}
|
||||
|
||||
/// An arena that can hold objects of only one type.
|
||||
///
|
||||
/// Safety note: Modifying objects in the arena that have already had their
|
||||
/// `drop` destructors run can cause leaks, because the destructor will not
|
||||
/// run again for these objects.
|
||||
pub struct TypedArena<T> {
|
||||
/// A pointer to the next object to be allocated.
|
||||
priv ptr: *T,
|
||||
|
||||
/// A pointer to the end of the allocated area. When this pointer is
|
||||
/// reached, a new chunk is allocated.
|
||||
priv end: *T,
|
||||
|
||||
/// The type descriptor of the objects in the arena. This should not be
|
||||
/// necessary, but is until generic destructors are supported.
|
||||
priv tydesc: *TyDesc,
|
||||
|
||||
/// A pointer to the first arena segment.
|
||||
priv first: Option<~TypedArenaChunk>,
|
||||
}
|
||||
|
||||
struct TypedArenaChunk {
|
||||
/// Pointer to the next arena segment.
|
||||
next: Option<~TypedArenaChunk>,
|
||||
|
||||
/// The number of elements that this chunk can hold.
|
||||
capacity: uint,
|
||||
|
||||
// Objects follow here, suitably aligned.
|
||||
}
|
||||
|
||||
impl TypedArenaChunk {
|
||||
#[inline]
|
||||
fn new<T>(next: Option<~TypedArenaChunk>, capacity: uint)
|
||||
-> ~TypedArenaChunk {
|
||||
let mut size = mem::size_of::<TypedArenaChunk>();
|
||||
size = round_up(size, mem::min_align_of::<T>());
|
||||
let elem_size = mem::size_of::<T>();
|
||||
let elems_size = elem_size.checked_mul(&capacity).unwrap();
|
||||
size = size.checked_add(&elems_size).unwrap();
|
||||
|
||||
let mut chunk = unsafe {
|
||||
let chunk = global_heap::exchange_malloc(size);
|
||||
let mut chunk: ~TypedArenaChunk = cast::transmute(chunk);
|
||||
intrinsics::move_val_init(&mut chunk.next, next);
|
||||
chunk
|
||||
};
|
||||
|
||||
chunk.capacity = capacity;
|
||||
chunk
|
||||
}
|
||||
|
||||
/// Destroys this arena chunk. If the type descriptor is supplied, the
|
||||
/// drop glue is called; otherwise, drop glue is not called.
|
||||
#[inline]
|
||||
unsafe fn destroy(&mut self, len: uint, opt_tydesc: Option<*TyDesc>) {
|
||||
// Destroy all the allocated objects.
|
||||
match opt_tydesc {
|
||||
None => {}
|
||||
Some(tydesc) => {
|
||||
let mut start = self.start(tydesc);
|
||||
for _ in range(0, len) {
|
||||
((*tydesc).drop_glue)(start as *i8);
|
||||
start = start.offset((*tydesc).size as int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy the next chunk.
|
||||
let next_opt = util::replace(&mut self.next, None);
|
||||
match next_opt {
|
||||
None => {}
|
||||
Some(mut next) => {
|
||||
// We assume that the next chunk is completely filled.
|
||||
next.destroy(next.capacity, opt_tydesc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a pointer to the first allocated object.
|
||||
#[inline]
|
||||
fn start(&self, tydesc: *TyDesc) -> *u8 {
|
||||
let this: *TypedArenaChunk = self;
|
||||
unsafe {
|
||||
cast::transmute(round_up(this.offset(1) as uint, (*tydesc).align))
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a pointer to the end of the allocated space.
|
||||
#[inline]
|
||||
fn end(&self, tydesc: *TyDesc) -> *u8 {
|
||||
unsafe {
|
||||
let size = (*tydesc).size.checked_mul(&self.capacity).unwrap();
|
||||
self.start(tydesc).offset(size as int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TypedArena<T> {
|
||||
/// Creates a new arena with preallocated space for 8 objects.
|
||||
#[inline]
|
||||
pub fn new() -> TypedArena<T> {
|
||||
TypedArena::with_capacity(8)
|
||||
}
|
||||
|
||||
/// Creates a new arena with preallocated space for the given number of
|
||||
/// objects.
|
||||
#[inline]
|
||||
pub fn with_capacity(capacity: uint) -> TypedArena<T> {
|
||||
let chunk = TypedArenaChunk::new::<T>(None, capacity);
|
||||
let tydesc = unsafe {
|
||||
intrinsics::get_tydesc::<T>()
|
||||
};
|
||||
TypedArena {
|
||||
ptr: chunk.start(tydesc) as *T,
|
||||
end: chunk.end(tydesc) as *T,
|
||||
tydesc: tydesc,
|
||||
first: Some(chunk),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates an object into this arena.
|
||||
#[inline]
|
||||
pub fn alloc<'a>(&'a self, object: T) -> &'a T {
|
||||
unsafe {
|
||||
let this = cast::transmute_mut(self);
|
||||
if this.ptr == this.end {
|
||||
this.grow()
|
||||
}
|
||||
|
||||
let ptr: &'a mut T = cast::transmute(this.ptr);
|
||||
intrinsics::move_val_init(ptr, object);
|
||||
this.ptr = this.ptr.offset(1);
|
||||
let ptr: &'a T = ptr;
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
/// Grows the arena.
|
||||
#[inline(never)]
|
||||
fn grow(&mut self) {
|
||||
let chunk = self.first.take_unwrap();
|
||||
let new_capacity = chunk.capacity.checked_mul(&2).unwrap();
|
||||
let chunk = TypedArenaChunk::new::<T>(Some(chunk), new_capacity);
|
||||
self.ptr = chunk.start(self.tydesc) as *T;
|
||||
self.end = chunk.end(self.tydesc) as *T;
|
||||
self.first = Some(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe_destructor]
|
||||
impl<T> Drop for TypedArena<T> {
|
||||
fn drop(&mut self) {
|
||||
// Determine how much was filled.
|
||||
let start = self.first.get_ref().start(self.tydesc) as uint;
|
||||
let end = self.ptr as uint;
|
||||
let diff = (end - start) / mem::size_of::<T>();
|
||||
|
||||
// Pass that to the `destroy` method.
|
||||
unsafe {
|
||||
let opt_tydesc = if intrinsics::needs_drop::<T>() {
|
||||
Some(self.tydesc)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.first.get_mut_ref().destroy(diff, opt_tydesc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Arena, TypedArena};
|
||||
use test::BenchHarness;
|
||||
|
||||
struct Point {
|
||||
x: int,
|
||||
y: int,
|
||||
z: int,
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_pod() {
|
||||
let arena = TypedArena::new();
|
||||
for _ in range(0, 1000000) {
|
||||
arena.alloc(Point {
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_pod(bh: &mut BenchHarness) {
|
||||
let arena = TypedArena::new();
|
||||
bh.iter(|| {
|
||||
arena.alloc(Point {
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_pod_nonarena(bh: &mut BenchHarness) {
|
||||
bh.iter(|| {
|
||||
let _ = ~Point {
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_pod_old_arena(bh: &mut BenchHarness) {
|
||||
let arena = Arena::new();
|
||||
bh.iter(|| {
|
||||
arena.alloc(|| {
|
||||
Point {
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3,
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
struct Nonpod {
|
||||
string: ~str,
|
||||
array: ~[int],
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_nonpod() {
|
||||
let arena = TypedArena::new();
|
||||
for _ in range(0, 1000000) {
|
||||
arena.alloc(Nonpod {
|
||||
string: ~"hello world",
|
||||
array: ~[ 1, 2, 3, 4, 5 ],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_nonpod(bh: &mut BenchHarness) {
|
||||
let arena = TypedArena::new();
|
||||
bh.iter(|| {
|
||||
arena.alloc(Nonpod {
|
||||
string: ~"hello world",
|
||||
array: ~[ 1, 2, 3, 4, 5 ],
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_nonpod_nonarena(bh: &mut BenchHarness) {
|
||||
bh.iter(|| {
|
||||
let _ = ~Nonpod {
|
||||
string: ~"hello world",
|
||||
array: ~[ 1, 2, 3, 4, 5 ],
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_nonpod_old_arena(bh: &mut BenchHarness) {
|
||||
let arena = Arena::new();
|
||||
bh.iter(|| {
|
||||
let _ = arena.alloc(|| Nonpod {
|
||||
string: ~"hello world",
|
||||
array: ~[ 1, 2, 3, 4, 5 ],
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
extern mod extra;
|
||||
|
||||
use std::iter::range_step;
|
||||
use extra::arena::Arena;
|
||||
use extra::future::Future;
|
||||
use extra::arena::TypedArena;
|
||||
|
||||
enum Tree<'a> {
|
||||
Nil,
|
||||
|
|
@ -26,14 +26,15 @@ fn item_check(t: &Tree) -> int {
|
|||
}
|
||||
}
|
||||
|
||||
fn bottom_up_tree<'r>(arena: &'r Arena, item: int, depth: int) -> &'r Tree<'r> {
|
||||
fn bottom_up_tree<'r>(arena: &'r TypedArena<Tree<'r>>, item: int, depth: int)
|
||||
-> &'r Tree<'r> {
|
||||
if depth > 0 {
|
||||
arena.alloc(|| {
|
||||
Node(bottom_up_tree(arena, 2 * item - 1, depth - 1),
|
||||
bottom_up_tree(arena, 2 * item, depth - 1),
|
||||
item)
|
||||
})
|
||||
} else {arena.alloc(|| Nil)}
|
||||
arena.alloc(Node(bottom_up_tree(arena, 2 * item - 1, depth - 1),
|
||||
bottom_up_tree(arena, 2 * item, depth - 1),
|
||||
item))
|
||||
} else {
|
||||
arena.alloc(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
@ -49,7 +50,7 @@ fn main() {
|
|||
let max_depth = if min_depth + 2 > n {min_depth + 2} else {n};
|
||||
|
||||
{
|
||||
let arena = Arena::new();
|
||||
let arena = TypedArena::new();
|
||||
let depth = max_depth + 1;
|
||||
let tree = bottom_up_tree(&arena, 0, depth);
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ fn main() {
|
|||
depth, item_check(tree));
|
||||
}
|
||||
|
||||
let long_lived_arena = Arena::new();
|
||||
let long_lived_arena = TypedArena::new();
|
||||
let long_lived_tree = bottom_up_tree(&long_lived_arena, 0, max_depth);
|
||||
|
||||
let mut messages = range_step(min_depth, max_depth + 1, 2).map(|depth| {
|
||||
|
|
@ -66,7 +67,7 @@ fn main() {
|
|||
do Future::spawn {
|
||||
let mut chk = 0;
|
||||
for i in range(1, iterations + 1) {
|
||||
let arena = Arena::new();
|
||||
let arena = TypedArena::new();
|
||||
let a = bottom_up_tree(&arena, i, depth);
|
||||
let b = bottom_up_tree(&arena, -i, depth);
|
||||
chk += item_check(a) + item_check(b);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue