khun
khun

Reputation: 23

Initialize a vector of struct with zero values in Rust

struct MyStruct {
    v1: u32,
    v2: u64,
}

type MyVector = Vec<MyStruct>;
impl Default for MyStruct {
    fn default() -> Self {
        Self {
            v1: 0,
            v2: 0,
        }
    }
}

fn init_my_vec() {
    let size = 1000;
    let mut my_vec: MyVector = Vec::with_capacity(size);
    (0..size).for_each(|_| my_vec.push(MyStruct::default()))
}
let usize_vec: Vec<usize> = vec![0; 1000];
// is faster than
let mut usize_vec: Vec<usize> = Vec::with_capacity(1000);
for i in 0..1000 {
    usize_vec.push(0);
}

Question

  1. Am I right about vector initialization speed? As fill with 0 is special instruction, using iterator is slower than using macro.
  2. Is there any method that can initialize the vector of struct with 0 values safely and fast?
  3. Or I should use unsafe code like making empty bytes and casting it to vector?

Speed measurement about Question 1

const VEC_SIZE: usize = 10_000;

fn init_with_iter() -> u128 {
    let start = Instant::now();
    let mut usize_vec: Vec<usize> = Vec::with_capacity(VEC_SIZE);
    for i in 0..VEC_SIZE {
        usize_vec.push(0);
    }
    start.elapsed().as_micros()
}

fn init_with_macro() -> u128 {
    let start = Instant::now();
    let _: Vec<usize> = vec![0; VEC_SIZE];
    start.elapsed().as_micros()
}

Average time taken to generate vector 10,000 times is


Speed measurement about Question 3

I think using unsafe function mem::zeroed is slightly faster than any others

const VEC_SIZE: usize = 10_000;

fn init_with_iter() -> u128 {
    let start = Instant::now();
    let mut my_vec: MyVector = Vec::with_capacity(VEC_SIZE);
    for _ in 0..VEC_SIZE {
        my_vec.push(MyStruct::default());
    }
    start.elapsed().as_micros()
}

fn init_with_macro() -> u128 {
    let start = Instant::now();
    let _: MyVector = vec![MyStruct::default(); VEC_SIZE];
    start.elapsed().as_micros()
}

fn init_with_zeroed() -> u128 {
    let start = Instant::now();
    let _: MyVector = unsafe { vec![std::mem::zeroed(); VEC_SIZE] };
    start.elapsed().as_micros()
}

Average time taken to generate vector 1,000 times is

Upvotes: 2

Views: 4460

Answers (1)

Svetlin Zarev
Svetlin Zarev

Reputation: 15713

Here is a criterion benchmark of your three approaches:

use criterion::{black_box, criterion_group, criterion_main, Criterion};

criterion_group!(
    benches,
    init_structs_with_iter,
    init_structs_with_macro,
    init_structs_with_unsafe
);
criterion_main!(benches);

const N_ITEMS: usize = 1000;

#[allow(unused)]
#[derive(Debug, Clone)]
struct MyStruct {
    v1: u32,
    v2: u64,
}

impl Default for MyStruct {
    fn default() -> Self {
        Self { v1: 0, v2: 0 }
    }
}

fn init_structs_with_iter(c: &mut Criterion) {
    c.bench_function("structs: with_iter", |b| {
        b.iter(|| {
            let mut my_vec = Vec::with_capacity(N_ITEMS);
            (0..my_vec.capacity()).for_each(|_| my_vec.push(MyStruct::default()));
            black_box(my_vec);
        })
    });
}

fn init_structs_with_macro(c: &mut Criterion) {
    c.bench_function("structs: with_macro", |b| {
        b.iter(|| {
            let my_vec = vec![MyStruct::default(); N_ITEMS];
            black_box(my_vec);
        })
    });
}

fn init_structs_with_unsafe(c: &mut Criterion) {
    c.bench_function("structs: with_unsafe", |b| {
        b.iter(|| {
            let my_vec: Vec<MyStruct> = vec![unsafe { std::mem::zeroed() }; N_ITEMS];
            black_box(my_vec);
        })
    });
}

And the results:

structs: with_iter      time:   [1.3857 us 1.3960 us 1.4073 us]                                
structs: with_macro     time:   [563.30 ns 565.30 ns 567.32 ns]                                 
structs: with_unsafe    time:   [568.84 ns 570.09 ns 571.49 ns]                                  

The vec![] macro seems to be the fastest (and also the cleanest and easiest to read).

As you can see, the time is measured in nanoseconds, so although the iterator version is 2-3x slower, it won't matter in practice. Optimizing the zero-initialization of a struct is the least important thing you can do - you can save at most 1 microsecond ;)

PS: those times include the memory allocation and deallocation times

Upvotes: 3

Related Questions