Vec and Box Slice Performance Comparison

I'm trying to learn Rust, and I'm slowly starting to realize how much fun it is. I want a function

  • allocate the main array of variable length (in the general sense of the word, not necessarily the type Rust) floats on the heap
  • initialize it with values
  • implement drop, so I don’t have to worry about freeing memory and something for indexing or iteration.

The obvious choice is Vec , but how does it compare to a piece in the heap? Vec more powerful, but I need an array for numerical mathematics and, in my case, I don’t need things like push / pop. The idea is to have something with less features, but faster.

Below I have two versions of the "linspace" function (a la Matlab and numpy),

  • "linspace_vec" (see list below) uses Vec
  • "linspace_boxed_slice" (see list below) uses a slice in a box

Both are used as

 let y = linspace_*(start, stop, len); 

where y is a linearly spaced "array" (i.e., a Vec in (1) and an insert in a block in (2)) of length len.

Now the question is: for small "arrays" of length 1000, (1) is FASTER. For large arrays 4 * 10 ^ 6 long, (1) is SLOWER. Why is this? Am I doing something wrong in (2)?

When the argument is len = 1000, comparative analysis by simply calling the function leads to

  • (1) ... bench: 879 ns / iter (+/- 12)
  • (2) ... bench: 1,295 ns / iter (+/- 38)

When the argument len = 4000000, the comparison results in

  • (1) ... bench: 5 802 836 ns / iner (+/- 90.209)
  • (2) ... bench: 4,767,234 ns / iner (+/- 121,596)

List (1):

 pub fn linspace_vec<'a, T: 'a>(start: T, stop: T, len: usize) -> Vec<T> where T: Float { // get 0, 1 and the increment dx as T let (one, zero, dx) = get_values_as_type_t::<T>(start, stop, len); let mut v = vec![zero; len]; let mut c = zero; let ptr: *mut T = v.as_mut_ptr(); unsafe { for ii in 0..len { let x = ptr.offset((ii as isize)); *x = start + c*dx; c = c + one; } } return v } 

Listing (2):

 pub fn linspace_boxed_slice<'a, T: 'a>(start: T, stop: T, len: usize) -> Box<&'a mut [T]> where T: Float { let (one, zero, dx) = get_values_as_type_t::<T>(start, stop, len); let size = len * mem::size_of::<T>(); unsafe { let ptr = heap::allocate(size, align_of::<T>()) as *mut T; let mut c = zero; for ii in 0..len { let x = ptr.offset((ii as isize)); *x = start + c*dx; c = c + one; } // IS THIS WHAT MAKES IT SLOW?: let sl = slice::from_raw_parts_mut(ptr, len); return Box::new(sl); } } 
+7
performance rust
source share
1 answer

In your second version, you use the type Box<&'a mut [T]> , which means there are two levels of indirection to achieve T , since both Box and & are pointers.

Instead, you need Box<[T]> . I think the only sensible way to build such a value is to Vec<T> using the into_boxed_slice method. Note that the only advantage is that you lose the capacity field that Vec will have. Unless you need to have many such arrays in memory at the same time, the overhead is likely to be negligible.

 pub fn linspace_vec<'a, T: 'a>(start: T, stop: T, len: usize) -> Box<[T]> where T: Float { // get 0, 1 and the increment dx as T let (one, zero, dx) = get_values_as_type_t::<T>(start, stop, len); let mut v = vec![zero; len].into_boxed_slice(); let mut c = zero; let ptr: *mut T = v.as_mut_ptr(); unsafe { for ii in 0..len { let x = ptr.offset((ii as isize)); *x = start + c*dx; c = c + one; } } v } 
+14
source share

All Articles