Rust difference between Std::ops for normal operators

Asked

Viewed 111 times

2

Hello I am seeking to optimize mathematical operations in my program, exploring the modules of the Rust found the Std:.

My question is this:.

use std::ops::{Add};
fn main() {
    let x = 10;
    println!{"{}",x.add(2)}; // retorna 12
    println!{"{}",x + 2}; // retorna 12
}

In both cases the output is the same

But there is some difference between the performance of x.add(2) to the x + 2 ?

Note there are other operators like % , / , - , * in the module Std:ops;

1 answer

3


TL;DR: No, there is no difference in performance (in the question example and other observation operators) when you compile with the option release.


The arithmetic operations in std::ops are defined through traits, for example, the sum operation:

#[doc(alias = "+")]
pub trait Add<Rhs=Self> {
    /// The resulting type after applying the `+` operator.
    #[stable(feature = "rust1", since = "1.0.0")]
    type Output;

    /// Performs the `+` operation.
    #[must_use]
    #[stable(feature = "rust1", since = "1.0.0")]
    fn add(self, rhs: Rhs) -> Self::Output;
}

The function add receives 2 parameters of the same type (Rhs=Self) and returns a result of the same type, too.

The implementation for the primitive types (usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64) are defined in the macro add_impl:

impl Add for $t {
    type Output = $t;

    #[inline]
    #[rustc_inherit_overflow_checks]
    fn add(self, other: $t) -> $t { self + other }
}

That is, it implements the sum through the operator itself + and, as there is the attribute #[inline], the compiler in mode release, optimizes the code and does not generate none performance difference.

Reference: https://github.com/rust-lang/rust/blob/master/src/libcore/ops/arith.rs


If you store the code in the mode debug (without any optimization), there is a small difference in the generated binary code that results in loss of performance.

To the code below:

use std::ops::{Add};

// Lê uma string numérica digitada pelo usuário e converte para inteiro de 64 bits
fn get_number() -> i64 {
    let mut buf = String::new();
    std::io::stdin().read_line(&mut buf).unwrap();
    buf.trim().parse::<i64>().unwrap()
}

// Função principal
fn main() {
    let x = get_number();
    let y = get_number();

    let opsadd = x.add(y);   // Soma através do método add
    let diradd = x + y;   // Soma direta

    println!("Direto: {}", diradd);
    println!("Ops: {}", opsadd);
}

Without optimization, it generates a call in the executable (call) to the function add to obtain the result and store in opsadd and, for the diradd the sum is made directly without calls.

This call is required because the function is compiled without the [inline] in the mode without optimization:

140002a20:   48 81 ec 28 01 00 00    sub    $0x128,%rsp
140002a27:   e8 c4 fe ff ff          callq  0x1400028f0      ; lê entrada do usuário
140002a2c:   48 89 44 24 68          mov    %rax,0x68(%rsp)  ; salva em x
140002a31:   e8 ba fe ff ff          callq  0x1400028f0      ; lê entrada do usuário
140002a36:   48 89 44 24 70          mov    %rax,0x70(%rsp)  ; salva em y
140002a3b:   48 8b 4c 24 68          mov    0x68(%rsp),%rcx
140002a40:   48 8b 54 24 70          mov    0x70(%rsp),%rdx  ; Carrega os 2 valores e...
140002a45:   e8 b6 ed ff ff          callq  0x140001800      ; ...chama o add
140002a4a:   48 89 44 24 78          mov    %rax,0x78(%rsp)  ; salva o resultado em opsadd
140002a4f:   48 8b 44 24 68          mov    0x68(%rsp),%rax  ; obtém o valor de x
140002a54:   48 03 44 24 70          add    0x70(%rsp),%rax  ; soma direto (+) com y
140002a59:   0f 90 c1                seto   %cl
140002a5c:   f6 c1 01                test   $0x1,%cl
140002a5f:   48 89 44 24 60          mov    %rax,0x60(%rsp)  ; salva o resultado em diradd
140002a64:   0f 85 51 01 00 00       jne    0x140002bbb

With optimization, the sum is made directly and the compiler, including, "understands" that the value of opsadd and diradd are the same:

 1400014b0:   56                      push   %rsi
 1400014b1:   57                      push   %rdi
 1400014b2:   48 83 ec 78             sub    $0x78,%rsp
 1400014b6:   e8 d5 fe ff ff          callq  0x140001390      ; lê entrada do usuário
 1400014bb:   48 89 c6                mov    %rax,%rsi        ; salva em x
 1400014be:   e8 cd fe ff ff          callq  0x140001390      ; lê entrada do usuário
 1400014c3:   48 01 f0                add    %rsi,%rax        ; já soma direto com y
 1400014c6:   48 89 44 24 38          mov    %rax,0x38(%rsp)  ; salva o resultado em opsadd
 1400014cb:   48 89 44 24 40          mov    %rax,0x40(%rsp)  ; salva o mesmo resultado em diradd

Therefore, without optimization there is an operation of call "", which generates a cost of performance during execution and, with optimization, this cost does not exist.

  • Understood I thought there would be some difference , for example in extensive calculations as an integral or derivative I thought that using these resources there would be an optimization.

  • I also noticed that the Boolean operators in the Std::ops modules like < , > , &&, || , != etc... The same case would then be valid :)

  • Yes, it is valid yes for boolean operators, which follow the same logic

Browser other questions tagged

You are not signed in. Login or sign up in order to post.