Search code examples
node.jsperformancerustneon-bindings

Do Rust addons in node make sense for compute heavy workloads?


So i am a noob when it comes to Rust or creating Node.js addons with it, so i started exploring Neon example here

I modified it a little to see how "compute bound cpu tasks" like nested for loop return any optimization if i write in Rust or JS by adding following code in my index.js

const cpuCount = require(".");

let st = Date.now();
let counter = cpuCount.get();
console.log(`Rust: ${Date.now() - st}ms ${counter}`);

st = Date.now();
let bigcounter = 0;
for (let index = 0; index < 1000_00; index++) {
    for (let internalIndex = 0; internalIndex < 1000_00; internalIndex++) {
        bigcounter += 1;
    }
}
console.log(`JS: ${Date.now() - st}ms ${bigcounter}`); 

similarly i updated the lib.rs to following:

use neon::prelude::*;

fn get_num_cpus(mut cx: FunctionContext) -> JsResult<JsNumber> {
let mut counter=0f64;
    for _n in 0..1000_00 {
        for _x in 0..1000_00 {
        counter+=1f64;
        }
    }
    Ok(cx.number(counter))
    //Ok(cx.number(num_cpus::get() as f64))
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    cx.export_function("get", get_num_cpus)?;
    Ok(())
}

and to my surprise i got following results:

Rust: 107258ms 10000000000
JS: 11056ms 10000000000

Hence the million dollar question do Rust addon make sense for CPU bound tasks?.. my assumption is JS over time have got fast enough to come within tolerable margins of compiled and typed languages?

PS: This is in no way a benchmark for languages, Just a specific scenario which i have where i am traversing through multidimensional array and computing some results. Node: 16 LTS Rust: 1.69.0

Update:

As @jmb suggested in the comments i was not running a release build, post which the results are as follows:

Rust: 9487ms 10000000000
JS: 10916ms 10000000000

Which is now marginally better than JS, I guess the question now is Does 1429ms justify the maintenance overhead of having 2 languages in one code base?


Solution

  • Does it worth it? Too many variables should be taken into account.

    1. What's the size of the task?

      • for small tasks (like yours) speed doesn't matter
      • small tasks / big time (like NP, brute-force) I would prefer Rust
      • big tasks? depends on whether the plugin copies the data from V8
    2. Can the solution be parallelized?

      • it's easy with Rust's rayon crate
      • not so easy with Nodejs, as it requires copying data between threads
    3. Are you okay with memory consumption?

      • v8 is greedy
      • garbage collector is a problem
    4. Is it a performance-critical application/module?

      • yes, it's the core high-load service
      • no, it is called once a month
    5. Have you tried to optimize the javascript code?

      • effective algorithms and data structures
      • give JIT a hand
    6. Do your team has expertise in Rust? Are you ready to gain it?

    You may also check napi-rs, it has a lower overhead and may run 2x faster than neon.