Search code examples
rustallocation

The impact of avoiding let variable bindings


Having fun with a project using gdnative, I wrote this:

#[method]
fn _save_player_position(&mut self, player_current_position: VariantArray) {
    let player_current_position: (f64, f64) = (
        player_current_position.get(0).to::<f64>().unwrap(),
        player_current_position.get(1).to::<f64>().unwrap()
    );
    self.player_data.set_player_position(player_current_position.0, player_current_position.1);
    self.received_signals += 1;
}

My doubt is, do you "win" some benefit by rewrite the code like this:

#[method]
fn _save_player_position(&mut self, player_current_position: VariantArray) {
    self.player_data.set_player_position(
        player_current_position.get(0).to::<f64>().unwrap(),
        player_current_position.get(1).to::<f64>().unwrap()
    );
    self.received_signals += 1;
}

As far as I know, I am avoiding:

  • The creation of a new tuple struct
  • Storing the data in it's unnamed fields
  • Saving the data on the let player_current_position
  • Then moving the data to some of the self fields

And the questions are:

  • Is the above true?
  • Is worth starting code like this in order to avoid allocations (even if they are in the stack)
  • Is better to only optimize heap ones, and improve readability whenever it's possible?

Solution

  • You can check the compiler output for both cases (slightly rewritten for clarity) here:

    https://godbolt.org/z/Gc4nr6afb

    struct Foo {
        pos : (f64, f64),
    }
    impl Foo {
        fn bar(&mut self, current : (f64, f64)) {
            let player_current_position: (f64, f64) = (
                current.0,
                current.1,
            );
            self.pos = (player_current_position.0, player_current_position.1);
        }
    
        fn bar2(&mut self, current : (f64, f64)) {
            self.pos = (current.0, current.1);
        }
    }
    
    
    pub fn main() { 
        let mut foo = Foo {pos: (1.0, 1.0)};
        foo.bar((2.0,2.0));
        foo.bar2((2.0,2.0));
    }
    

    this is with local variables:

    Foo::bar:
            sub     rsp, 16
            movsd   qword ptr [rsp], xmm0
            movsd   qword ptr [rsp + 8], xmm1
            movsd   xmm1, qword ptr [rsp]
            movsd   xmm0, qword ptr [rsp + 8]
            movsd   qword ptr [rdi], xmm1
            movsd   qword ptr [rdi + 8], xmm0
            add     rsp, 16
            ret
    

    and this is without

    Foo::bar2:
            movsd   qword ptr [rdi], xmm0
            movsd   qword ptr [rdi + 8], xmm1
            ret
    

    Note that as per @Jmb comment, once compiler optimisation is enabled, the output will be identical: https://godbolt.org/z/xKPfMP6Yr