Which would have better performance, Heapless's Vector, Or an a Array?

So I realized that My program's vectors didn't change their size throughout the program. So I decided to optimize, and use arrays.
But that would require me to rewrite a lot of My code, So I am considering using the heapless crate's vector, So that I wouldn't have to rewrite as much code.

Are there any performance differences?

The difference is that the heapless has an extra integer for the length and that it supports changing its length. If you don't need those things, use an array.

Even fixed length scenarios used to be unsuitable for non Copy arrays, but this has slowly been fixed with the introduction of array destructing, FromIterator impls for arrays, const generics and now the map method in 1.55. Arrays in modern rust have finally almost caught up to vectors in terms of expressivity, and I suspect[1] they are now perfectly viable for general usage.

If you find that there are a small few places in your code that need to resize something, you can still use Heapless there and convert to and from arrays.


  1. I say "I suspect" because personally, the vast majority of my own array heavy code was written before the introduction of these conveniences, so I've always had to write my own wrapper types around arrays in order to make them useful. ↩︎

That's histerical, hadn't seen heapless before. But I see the advantage over array and Vec. I'm not 100% sure passing fixed-sized arrays around is as efficient for very very large arrays (or if the sum of all arrays in a call stack exceeds the rust compiled limit).. In fact I'm pretty sure I've crashed rust when allocating multi-GB fixed sized arrays.

#[test]
fn test1() {

    const N: usize = 1usize << 30;
    if true {
        use heapless::Vec;
        let mut xs: Vec<u8, N> = heapless::Vec::new();

        for x in 0..N {
            xs.push(0);
        }
        println!("probe[0]={} [-1]={}",xs[0], xs[N-1]);
    } else {
        let mut xs = Vec::with_capacity(N);
        for x in 0..N {
            xs.push(0);
        }
        println!("probe[0]={} [-1]={}",xs[0], xs[N-1]);
    }


}

Crashes for me .. if I set N to 2^20 it works fine for both cases. For 2^30 only the Vec works (because the call-stack-size gets exceeded).

If your arrays are ALWAYS fixed size, I'd say use fixed sized arrays instead of using 3rd party libraries. (I love using std:: in all my programming languages since you don't always have access to the internet). But for the dynamic array cases (where I'd normally reach for Vec), yeah this seems plausible. Again, only if you're confident the stack isn't going to explode (e.g. no unguarded user-input-data). Whole point of Rust was to make the internet a safer place after all. :open_mouth:

Though I would say Box<heapless::Vec> is pointless; and similarly for ANY boxed struct. You're saving 8 bytes by going to heaplessVec v.s. std::Vec and confusing the heck out of people (and requiring extra generic type signature or WORST const generic signatures everywhere or WORSE type aliases - just let C die already...)

There is an interesting comment in heapless source code saying the order of initialized fields defines how the struct is initialized. This is a good point.. If you have a struct with heapless, this might have a performance impact on it's instantiation...

struct Foo {
   data: heapless::Vec<u8, 128>,
   name: String // v.s. putting `name` ahead of data ????
}

fn foo(name:String) {
    let foo = Foo { name, data: heapless::Vec::new() } // DOES THIS memset??
}

Not sure, but feels like it's really meant to use as stated, directly on the lexical call stack (except for the SUPER small, (less than 1 cache-line)). I don't know if there are performance implications v.s. the compiler-aware [0u8; N] type initializers - which I'm sure are battle-tested by the rust/llvm team. Here's a magic bit of [MaybeUnInit<T> ; N] magic that may hit wierd edge cases in a struct of a struct of a struct.

Interesting topic none the less. :slight_smile: