Reputation: 23
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
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
init_with_iter
): 514.6805 msinit_with_macro
): 2.0361 msQuestion 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
init_with_iter
): 575.572 ms.init_with_macro
): 486.958 msinit_with_zeroed
): 468.885 msUpvotes: 2
Views: 4460
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