Description
They are currently represented them as *{ length, capacity, data[] }
. It means we have to handle trickier uint
overflow cases, which increases cost size a fair bit. It also means even an empty vector always requires a heap allocation.
C++ represents a vector as { length, capacity, *data }
, so a fresh empty vector simply as a null
data pointer. Unlike the OptVec
optimization, it does not require an extra branch in every method. Calling realloc
on a null pointer is well-defined, so you can ignore the special case.
This has the drawback of making a move involve a 3-word copy, not 1-word. However, passing around unique vectors by-move rather than slices is not common enough to be a very relevant performance issue.
Benchmarks on x86_64 Linux (linux 3.11.5, glibc 2.18):
Comparison | ~[T] | Proposal |
---|---|---|
Empty | 29 ns/iter (+/- 1) | 2 ns/iter (+/- 0) |
Push 10k, starting at 16 capacity | 45528 ns/iter (+/- 2584) | 7611 ns/iter (+/- 407) |
Push 10k, starting at 10k capacity | 43783 ns/iter (+/- 1429) | 6876 ns/iter (+/- 320) |
Size | size_of::<uint> |
3 * size_of::<uint> |
Empty heap allocation size | minimum 2 * size_of::<uint> |
n/a |
4 element heap allocation size | 2 * size_of::<uint> + 4 * size_of::<T> |
4 * size_of::<T> |
Note that since jemalloc is very clever, at some sizes preallocating has next to no advantage for the new proposed vector too.
Implementation:
#[allow(ctypes)];
#[cfg(test)]
extern mod extra;
use std::mem::size_of;
use std::cast::transmute;
use std::unstable::raw::Slice;
use std::unstable::intrinsics::move_val_init;
use std::ptr::read_ptr;
// redefine these to use `*mut` ...
extern {
fn malloc(size: uint) -> *mut u8;
fn realloc(ptr: *mut u8, size: uint) -> *mut u8;
fn free(ptr: *mut u8);
fn abort() -> !;
}
#[fixed_stack_segment]
unsafe fn malloc_raw(size: uint) -> *mut u8 {
let ptr = malloc(size);
if ptr.is_null() {
abort()
}
ptr
}
#[fixed_stack_segment]
unsafe fn realloc_raw(ptr: *mut u8, size: uint) -> *mut u8 {
let ptr = realloc(ptr, size);
if ptr.is_null() {
abort()
}
ptr
}
#[fixed_stack_segment]
unsafe fn free_raw(ptr: *mut u8) {
free(ptr)
}
struct Vec<T> {
priv len: uint,
priv cap: uint,
priv ptr: *mut T
}
impl<T> Vec<T> {
#[inline]
fn new() -> Vec<T> {
Vec { len: 0, cap: 0, ptr: 0 as *mut T }
}
#[inline]
fn with_capacity(capacity: uint) -> Vec<T> {
let size = capacity.checked_mul(&size_of::<T>()).unwrap();
unsafe {
Vec { len: 0, cap: capacity, ptr: malloc_raw(size) as *mut T }
}
}
#[inline]
fn shrink_to_fit(&mut self) {
unsafe {
self.cap = self.len;
if self.len == 0 {
free_raw(self.ptr as *mut u8);
self.ptr = 0 as *mut T;
} else {
self.ptr = realloc_raw(self.ptr as *mut u8, self.cap * size_of::<T>()) as *mut T;
}
}
}
#[inline]
fn push(&mut self, value: T) {
unsafe {
if self.len == self.cap {
if self.cap == 0 { self.cap += 2 }
let old_size = self.cap * size_of::<T>();
self.cap = self.cap * 2;
let size = old_size.checked_mul(&2).unwrap();
self.ptr = realloc_raw(self.ptr as *mut u8, size) as *mut T;
}
let end = self.ptr.offset(self.len as int);
move_val_init(&mut *end, value);
self.len += 1;
}
}
#[inline]
fn as_slice<'r>(&'r self) -> &'r [T] {
let slice = Slice { data: self.ptr as *T, len: self.len };
unsafe { transmute(slice) }
}
#[inline]
fn as_mut_slice<'r>(&'r mut self) -> &'r mut [T] {
let slice = Slice { data: self.ptr as *T, len: self.len };
unsafe { transmute(slice) }
}
}
#[unsafe_destructor]
impl<T> Drop for Vec<T> {
fn drop(&mut self) {
unsafe {
for x in self.as_mut_slice().mut_iter() {
read_ptr(x);
}
free_raw(self.ptr as *mut u8)
}
}
}
#[cfg(test)]
mod bench {
use std;
use super::Vec;
use extra::test::BenchHarness;
#[bench]
fn empty(bh: &mut BenchHarness) {
do bh.iter() {
let _xs: Vec<int> = Vec::new();
}
}
#[bench]
fn empty_std(bh: &mut BenchHarness) {
do bh.iter() {
let _xs: ~[int] = ~[];
}
}
static size: uint = 10000;
#[bench]
fn push(bh: &mut BenchHarness) {
do bh.iter() {
let mut xs = Vec::with_capacity(16);
for i in range(0, size) {
xs.push(i);
}
}
}
#[bench]
fn push_std(bh: &mut BenchHarness) {
do bh.iter() {
let mut xs = std::vec::with_capacity(16);
for i in range(0, size) {
xs.push(i);
}
}
}
#[bench]
fn push_preallocated(bh: &mut BenchHarness) {
do bh.iter() {
let mut xs = Vec::with_capacity(size);
for i in range(0, size) {
xs.push(i);
}
}
}
#[bench]
fn push_preallocated_std(bh: &mut BenchHarness) {
do bh.iter() {
let mut xs = std::vec::with_capacity(size);
for i in range(0, size) {
xs.push(i);
}
}
}
}
#[test]
fn push() {
let mut xs = Vec::new();
for x in std::iter::range_step(0, 100, 5) {
xs.push(x)
}
let expected = std::iter::range_step(0, 100, 5).to_owned_vec();
assert_eq!(xs.as_slice(), expected.as_slice());
}