Search code examples
rustvector

Why is vec![value; size] not always the fastest way to initialize a vector in Rust?


After some refactoring, I was puzzled by a performance loss by some seemingly innocuous changes in my code. As it turns out, it seemed to have been caused by this code:

let mut frame = Self {
    locals: vec![Value::Nil; block.blk_info.nb_locals],
    ...
};

...which is apparently slower than THIS code:

let mut frame = Self {
    locals: (0..block.blk_info.nb_locals).map(|_| Value::Nil).collect(),
    ...
};

Is this normal? Should the vec! macro not be the most optimized way of initializing a vector with n default values?

I ran benchmarks on both versions of the code, and the second version is in fact faster. If no one is able to reproduce this, it may be caused by erroneous reports by my benchmark runner, but I'm sceptical that it is. I didn't try looking at the generated assembly though, since that would be quite time-consuming, but I might do it if I get no answers and I get too curious.


Solution

  • This can happen when Value has a non-straightforward Clone implementation (for example, an enum with a variant that contains String).

    When the type is not Copy, the vec![] macro clones the provided value for each element (and reuse it for the last, to save a clone).

    If LLVM fails to optimize the Clone implementation to return the same value provided (without branches or anything), it will be cheaper to just create a fresh new value every time, which is what the second snippet does.

    The second snippet will also (like the first snippet) avoid growth check, because ranges are TrustedLen.