1

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?

Laukik
  • 424
  • 4
  • 13
  • 1
    Did you compile the Rust code in release mode? – Jmb Apr 21 '23 at 06:39
  • 1
    Increasing a number isn't exactly a complex operation and I hope loops are well optimized by the js jit compiler so your benchmark might just be too simple to show any advantages. – cafce25 Apr 21 '23 at 06:44
  • @cafce25 Thats exactly the point inc would be just one instruction set on CPU for both languages which mean the added time is all language specific overheads which are been shown... Its just a perspective.. – Laukik Apr 21 '23 at 06:48
  • @jmb Good catch i have not build it in release mode do you know how to do it for this example https://github.com/neon-bindings/examples/tree/main/examples/cpu-count sorry new to Rust.. – Laukik Apr 21 '23 at 06:55
  • Sorry, I don't know Neon. For regular Rust projects, you would build them with `cargo build --release`, if Neon uses `cargo build` too then that should work, otherwise I don't know. – Jmb Apr 21 '23 at 07:00
  • @jmb did it just update this command in package.json ` "build": "cargo-cp-artifact -nc index.node -- cargo build -r --message-format=json-render-diagnostics",` – Laukik Apr 21 '23 at 07:01
  • Looks like neon uses `npm run build -- --release`: https://neon-bindings.com/docs/hello-world/#try-it-out – drewtato Apr 21 '23 at 07:04
  • 3
    1429ms is still a ~10% improvement for code that is ridiculously easy for nodejs to optimize. In fact I suspect that if you put the JS code in a function and call it multiple times, it will eventually become instantaneous (because the jit will simply cache the result instead of computing it each time). For real-life computations you should see a greater difference. – Jmb Apr 21 '23 at 07:46

1 Answers1

1

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.

xamgore
  • 1,723
  • 1
  • 15
  • 30
  • I completely understand its a can be complicated answer with many variables, hence i narrowed it down to my use-case of traversing through multi dimensional array.. seems in my use case it may be the last optimisation that i will think of before jumping on to RUST cause in my opinion we need considerable time and effort to master a new language.. any how "Depends" can be accepted as a generic answer which always is :) – Laukik Apr 25 '23 at 06:11
  • 1
    Just to correct on point 2, JS also has "Shared Buffer Array" where you can share things between threads its low level, i don't know how it compares with rayon crate you mentioned though.. – Laukik Apr 25 '23 at 06:13
  • 1
    Do you want to experiment together? Write me t.me/strebz, I'll help :) – xamgore Apr 26 '23 at 00:58
  • 1
    Thats some first hand love that i experienced on S.O till date ;) ; Appreciate the offer but for this case i think i am closer to conclusion – Laukik Apr 27 '23 at 13:31