Rust performance vs Julia

132 Views Asked by At

I am new to Rust and wrote a function for calculating exponential moving average in Rust and compared this to equivalent one in Julia for time taken to process a vector of 100,000 elements.

The Julia function gives me a time of approx 206 micro seconds whereas Rust gives me time of approx 2.27 milli seconds. Here is the Rust function -

use rand::Rng;
use std::time::Instant;

fn calculate_ema(prices: &[f64], alpha: f64) -> Vec<f64> {
    if prices.is_empty() {
        return vec![];
    }
    let mut ema = Vec::with_capacity(prices.len());
    ema.push(prices[0]); // The first EMA value is just the first price.

    for i in 1..prices.len() {
        let new_ema = alpha * prices[i] + (1.0 - alpha) * ema[i - 1];
        ema.push(new_ema);
    }
    ema
}

fn main() {
    // Generate a vector of 50,000 random prices between 10 and 50
    let mut rng = rand::thread_rng();
    let prices: Vec<f64> = (0..100000).map(|_| rng.gen_range(10.0..=50.0)).collect();

    // Define the period for the EMA
    let period = 10.5;

    // Measure the time taken to run the calculate_ema function
    let start_time = Instant::now();
    let _ema = calculate_ema(&prices, 2.0/(1.0 + period));
    let elapsed_time = start_time.elapsed();

    println!("Time taken to run calculate_ema: {:?}", elapsed_time);
}

And here is the equivalent Julia function -

using Random
using BenchmarkTools

function calculate_ema(prices, alpha)
    ema = Vector{Float64}(undef, length(prices))
    ema[1] = prices[1]
    for i in 2:length(prices)
        ema[i] = alpha * prices[i] + (1 - alpha) * ema[i-1]
    end
    return ema
end

prices = rand(10.0:0.1:50.0, 100000)

alpha = 2/(1 + 10.5)

# Measure the time taken to run the calculate_ema function
@btime calculate_ema(prices, alpha)

I was expecting Rust performance to be better than Julia or may be equal. I am wondering why Rust is 10 times slower here.

1

There are 1 best solutions below

0
ChrisB On

The main difference between the two pieces of code is that in rust you append to the vector on the fly, where in julia you first created the vector, and then updated the elements. The latter gives the optimizer (LLVM in both cases) a much better Idea of what you are doing, allowing for better code generation.

I adjusted your Rust code as follows, to make it do roughly the same thing as julia:

fn calculate_ema(prices: &[f64], alpha: f64) -> Vec<f64> {
    if prices.is_empty() {
        return vec![];
    }
    let mut ema = vec![0f64; prices.len()];
    ema[0] = prices[0];

    for i in 1..prices.len() {
        let new_ema = alpha * prices[i] + (1.0 - alpha) * ema[i - 1];
        ema[i] = new_ema;
    }
    ema
}

I get a runtime of 380us on the Rust playground . (down from 980us using the original version). For further optimization you could compare the assembly, maybe using the fantastic https://godbolt.org which supports both languages.