←back to thread

160 points todsacerdoti | 2 comments | | HN request time: 0s | source
Show context
roca ◴[] No.41902494[source]
I think the importance of parallelism has been overlooked by the OP and most commenters here. Even laptops these days have at least 8 cores; a good scalable parallel implementation of a tool will crush the performance of any single-threaded implementation. JS is not a great language for writing scalable parallel code. Rust is. Not only do Rust and its ecosystem (e.g. Rayon) make a lot of parallel idioms easy, Rust's thread safety guarantees let you write shared-memory code without creating a maintenance nightmare. In JS you can't write such code at all.

So yes, you can do clever tricks with ArrayBuffers, and the JS VMs will do incredibly clever optimizations for you, but as long as your code is running on one core you cannot be competitive. (Unless your problem is inherently serial, but very few "tool"-type problems are.)

replies(3): >>41902969 #>>41903945 #>>41904571 #
nine_k ◴[] No.41903945[source]
OTOH in production settings you can run 6-8 copies of your Node app to utilize the 8 physical CPU cores without (much) contention; the JS engine runs additional threads for GC and other housekeeping. Or you can use multiple "web workers" which are VM copies in disguise. The async nature of the JS runtime may allows for a lot of extra parallelism (for waiting on I/O completion) on one core if your load is I/O-bound, as it is the case for most web backends.

Th same does not hold for frontend use, as it's for one user, and latency trumps throughput in the perception of being fast. You need great single-thread performance, and an ability to offload stuff to parallel threads where possible, to keep the overall latency low. That's basically the approach of game engines.

replies(1): >>41904323 #
chrismorgan ◴[] No.41904323[source]
When talking about tooling: around five years ago, I introduced parallelism to a Node.js-based build system, in a section that was embarrassingly parallel. It was really painful to implement, made it noticeably harder to maintain, and my vague memory is that on an 4-core/8-thread machine I only got something like a 3–4× speedup. I think workers are mature enough now that it wouldn’t be quite so bad, but it would still be fairly painful.

In Rust, I’d have added Rayon as a dependency to my Cargo.toml, inserted `use rayon::prelude::;` (or a more specific import, if I preferred) into my file, changed one `.iter()` to `.par_iter()`, and voilà, it’d have compiled (all the types would have satisfied Send) and given probably at least a 6–7× speedup.

Seriously, when you get to talking about a lot of performance tricks and such (I’m thinking things like the bit maps referred to at the end), even when they’re possible* in JavaScript, they’re frequently—I suspect even normally—way easier to implement in Rust.

replies(3): >>41906156 #>>41906278 #>>41909582 #
jpc0 ◴[] No.41909582[source]
Rayon abstracts away the parallelism for you. Yes Parallelism is easier in compiled languages with a concept of threads but your example doesn't do that much justice.

Someone could write "rayon" for webworkers.

replies(1): >>41911637 #
chrismorgan ◴[] No.41911637[source]
> Someone could write "rayon" for webworkers.

Not true. JavaScript’s threading model is entirely insufficient for something like Rayon. At best, you could get either something that only worked with shared byte arrays, or something that was vastly less efficient due to structured clone. At best, you have something far more manual and somewhat slower, or something a little more manual and much slower.

Rayon is a magnificent example of making something that is impossible in scripting languages easy. Of making something that you can only feebly imitate with some difficulty, trivial.

replies(1): >>41912450 #
1. jpc0 ◴[] No.41912450{3}[source]
> only feebly imitate with some difficulty, trivial.

My point was the API simplicity not the technical correctness, which is why my post discussed threading in the first place.

Yes Rayon isn't possible in JS, but a "rayon" api like multi-threaded library that you can reach for in cases where it makes sense is absolutely doable.

replies(1): >>41912858 #
2. chrismorgan ◴[] No.41912858[source]
Having thought further about this, I’m going to double down on this, because it’s worse than I was even contemplating. What you describe would be as like Rayon as a propped-up, life-size cardboard cutout of a house, is like the depicted house.

Rayon’s approach lets you write code that will run in arbitrary other threads, inline and wherever you want to. That’s absolutely essential to Rayon’s API, but you can’t do that in JavaScript, at all: workers don’t execute the same code (it’s not based on forking), and interaction between workers is limited to transferable objects, or things that work with structured clone, which excludes functions.

No, you can’t get anything even vaguely like Rayon in JavaScript. You could get a feeble and hobbled imitation with untenable limitations or extra compilation step requirements (and still nasty limitations), and that’s about it.

With Rayon, you can add parallelism to existing code trivially. With JavaScript, the best you can manage, which is nowhere near as powerful or effective even then, requires that you architect your entire program differently, significantly differently in many cases, and in ways that are generally quite a bit harder to maintain.

If you wish to contest this, if you reckon I’ve overlooked something, I’m open to hearing. I’m looking for something along these lines to work:

  import { f1, f2 } from "./f.js";
  let n1 = Math.random();
  let n2 = Math.random();

  await par_iter([1, 2, 3, 4, 5])
      .map(n => f1(n + n1))
      .filter(n => f2(n + n2))
      .collect();
Where the mapping and filtering will be executed in a different worker, and collect() gives you back a Promise<Array>. The fact that f1 and f2 are defined elsewhere is deliberate—if it didn’t close over any variables, you could just stringify the function and recompile it in the worker.