Which implementation is faster?

I wanted to check how best to optimize my main project, and decided to check which implementation is faster. But I always get different results.

time for point.x += 0.1 = 5.7182994, 2097152
time for point.set_x(point.get_x() + 0.1) = 5.599908, 2097152
time for temp += 0.1 and point.set_x(temp) = 5.562349, 2097152
time for temp += 0.1 and point.x = temp = 6.4794574, 2097152

time for point.x += 0.1 = 5.83212, 2097152
time for point.set_x(point.get_x() + 0.1) = 5.5968776, 2097152
time for temp += 0.1 and point.set_x(temp) = 5.594217, 2097152
time for temp += 0.1 and point.x = temp = 5.5190163, 2097152

time for point.x += 0.1 = 5.6057734, 2097152
time for point.set_x(point.get_x() + 0.1) = 5.5254517, 2097152
time for temp += 0.1 and point.set_x(temp) = 5.528735, 2097152
time for temp += 0.1 and point.x = temp = 5.5161138, 2097152

time for point.x += 0.1 = 5.5781226, 2097152
time for point.set_x(point.get_x() + 0.1) = 5.6262784, 2097152
time for temp += 0.1 and point.set_x(temp) = 5.5054255, 2097152
time for temp += 0.1 and point.x = temp = 5.69275, 2097152

Which implementation is faster?

Code

use std::time::Instant;

trait ObjectInterface {
    fn get_x(&self) -> f32;
    fn set_x(&mut self, x: f32);
}

struct Point {
    x: f32,
    _y: f32
}

impl ObjectInterface for Point{
    fn get_x(&self) -> f32 {
        self.x
    }

    fn set_x(&mut self, x: f32) {
        self.x = x;
    }
}

fn add_one_million_time() {
    let mut point: Point = Point {
        x: 0.0,
        _y: 0.0
    };

    let now = Instant::now();
    // for _ in -2147483648..=2147483647 {
        for _ in -2147483648..=2147483647 {
            point.x += 0.1;
        }
    // }
    println!("time for point.x += 0.1 = {}, {}", Instant::now().duration_since(now).as_secs_f32(), point.x);

    point.x = 0.0;

    let now = Instant::now();
    // for _ in -2147483648..=2147483647 {
        for _ in -2147483648..=2147483647 {
            point.set_x(point.get_x() + 0.1);
        }
    // }
    println!("time for point.set_x(point.get_x() + 0.1) = {}, {}", Instant::now().duration_since(now).as_secs_f32(), point.x);

    point.x = 0.0;

    let now = Instant::now();
    let mut temp: f32 = point.get_x();
    // for _ in -2147483648..=2147483647 {
        for _ in -2147483648..=2147483647 {
            temp += 0.1;
        }
    // }
    point.set_x(temp);
    println!("time for temp += 0.1 and point.set_x(temp) = {}, {}", Instant::now().duration_since(now).as_secs_f32(), point.x);

    point.x = 0.0;

    let now = Instant::now();
    let mut temp: f32 = point.x;
    // for _ in -2147483648..=2147483647 {
        for _ in -2147483648..=2147483647 {
            temp += 0.1;
        }
    // }
    point.x = temp;
    println!("time for temp += 0.1 and point.x = temp = {}, {}", Instant::now().duration_since(now).as_secs_f32(), point.x);
}

fn main() {
    add_one_million_time()
}

If you get too noisy or inconsistent results, then there is likely no statistically significant difference. Consider using a statistically-sound benchmarking suite such as Criterion.

8 Likes

I am pretty sure that both versions of your implementation compile down to the very same code.

2 Likes

They do indeed.

3 Likes

Thank you! I used Criterion and got an interesting result:
-For the first implementation: time: [273.24 ms 282.18 ms 292.06 ms]
-For the second implementation: time: [265.31 ms 272.52 ms 280.43 ms]
-For the third implementation: time: [270.72 ms 278.34 ms 286.84 ms]
-For the fourth implementation: time: [289.20 ms 301.09 ms 313.77 ms]

So basically, the CI of the first three overlap, so no statistically significant difference. The last one technically appears to be slower than the 2nd and 3rd ones (likely by pure chance), although its CI overlaps with that of the first one. Overall, this is expected given that they compile down to the exact same machine code.

1 Like