Maybe you don't need Rust and WASM to speed up your JS

youngtaff | 600 points

I think this is an interesting exploration because I really enjoyed the description of profiling and improving performance, but I came away feeling exactly the opposite of the title. I think it's really cool that JS optimization can provide so many wins, but this article makes it seem fairly fickle and if they're not familiar with VM internals I would not expect most developers to complete this journey. Using wasm+rust/c++ is interesting in part because you can get so much more predictable performance out of the box. When the performance comes from language features instead of VM-specific optimizations the performance might be easier to maintain across revisions and hopefully will be less subject to regression from VM changes.

anp | 6 years ago

The problem with these tricks is that they're dependant on V8 internals, which means that there's no guarantee that it will be fast on Firefox (or edge, or even Safari).

So now if google changes the back end, websites will slow down, which means that Google has to ossify implementation details, furthering this sort of black magic into CS lore forever.

This, honestly, is what I hate with modern dev. A few days back there was a discussion on how programming is hard nowadays.

Really, programming is more accessible than ever. What is hard is the "black magic" that's becoming more and more prevalent, most of which is implementation defined, that everyone is expected to know (and really, most don't really know anything. They just repeat rumors they read about online, often years out of date).

greenhouse_gas | 6 years ago

> Is it better to quick-sort one array with 100K elements or quick-sort 3333 30-element subarrays?

> A bit of mathematics can guide us (100000log100000 is 3 times larger than 3333×30log30)

You have to be careful doing this kind of analysis. Big-O is explicitly about what happens for large values of N. It is traditional to leave off smaller factors, because they don't matter at the limit of N going to infinity. There is implicitly a constant coefficient on each component, and that might matter more at small N.

So e.g. I've seen cases that were O(N^2 + N), and which of course you'd traditionally write as O(N^2), but where the O(N) factor mattered more at small values of N because of constant factors. Depending on whether you cared more about small or large values of N, would guide whether you'd actually want to go after the O(N^2) factor or not. If you just blindly went for the larger factor, you could waste a lot of time and not actually accomplish anything.

eslaught | 6 years ago

The problem with this kind of deep dive optimization is the cost of maintaining it in a long-lived project as the underlying javascript engines keep changing. What was optimal for one version of V8 can actually be detrimental in another version, or in another browser.

It's precisely the unpredictability of JIT-driven optimizations that makes WASM so appealing. You can do all your optimizing once at build time and get consistent performance every time it runs.

It's not that plain Javascript can't be as fast -- it's that plain Javascript has high variance, and maintaining engine-internals-aware optimization in a big team with a long-lived app is impractical.

ef4 | 6 years ago

This is a great article and goes to show that statements like "X language is fast" are a little blurry when you put the language in the hands of a skilled developer, who can go beyond the standard idioms and surface level understanding of a language to use it like it's another language.

At the end of the article, the author wisely chooses to move some objects out of the control of the GC:

We are allocating hundreds of thousands Mapping objects, which puts considerable pressure on GC - in reality we don’t really need those objects to be objects... First of all, I changed Mapping from a normal object into a wrapper that points into a gigantic typed array...

This suggests to me that we are no longer programming the way one usually does in a dynamically typed, garbage collected language -- and thus it might still be the right decision to move to something like Rust (or Swift or Go) where there is considerably more control over allocation.

The author is able to achieve a speed-up of ~4x which is close to the 5.89x achieved by the Rust implementation. There are benefits to having all ones code in the same language; but there are also benefits to switching languages to obtain better ergonomics and safety properties.

solidsnack9000 | 6 years ago

I wonder if the performance gain is worth the effort. Surely you can always squeeze more performance from JS like from any other language but what's the point if you spend hours to match the performance you get for "free" from other languages?

As far as WASM is concerned I'm more excited about the possibility to run any programming language on the web than the raw performance gains. So far it is still year(s) away from this goal(i.e. lack of web APIs/DOM access makes it useless for web dev).

themihai | 6 years ago

I'm excited about compiling to WASM not for performance but for correctness. Typescript is better than nothing but it really can't compete with the safety and ease you get from a language with a really good type system.

lmm | 6 years ago

I thought all the benchmarks indicated that WASM is still slower than JS. That being said the only performance improvements offered by WASM is if it doesn't for garbage collection for consistence execution.

WASM isn't about performance. It is about writing applications in any language and importing those applications into an island in a web page.

austincheney | 6 years ago

So much work to achieve what should be default behavior :-/

How did we end up in wasting so much time on trivialities?

bitL | 6 years ago

This is a well written article, and I absolutely agree with the idea that profiling and analysis is more important than language choice in order to eke out performance wins.

That being said, some of these optimization techniques completely took me by surprise. Defining the sort function as a cache lookup that converts the sorting template to a string and then builds an anonymous function out of that string which is finally used as the exported function seems, to me, like an extremely roundabout way to achieve inlining the comparator. And the argument-count adapter having such high overhead on V8 seems like something that should generate a warning for the developer.

The cache analysis and algorithmic improvements seemed fairly straight forward, but when you're at the point of having to manually implement memory buffers to alleviate GC pressure, you're diving below the level of abstraction that the language itself provides you. At that point, I think the argument to switch to a language designed to operate at that level of abstraction holds some sway.

NiceGuy_Ty | 6 years ago

What a well-designed and well-researched article!

FeepingCreature | 6 years ago

We should be more wary about premature optimizations, like in the article where caching in the original code made it slower! Always measure! Write naive code and measure, the JavaScript engines are very good at optimization, especially V8 and the others are catching up.

However when I do optimize JavaScript code I often get 10-100x performance. Usually by writing better algorithms. Eg no "black magic". So the original code in the article is not that bad, considering he "only" got 4x performance.

Moving to another programming language / WASM for less then 2x performance is not worth it - unless you hate JavaScript.

z3t4 | 6 years ago

Tangent: I see that the author focused on improving sorting algorithms, and also at some point switches to a Uint8Array (although not in the sorting part).

I recently discovered that a JavaScript implementation of radix sort up to four times faster than the the built-in sorting algorithms for TypedArrays[0][1][2]. Imagine how much faster a good WASM implementation could be!

It also makes me wonder why browsers don't make use of the guaranteed memory layout of typed arrays to use faster native algorithms. Sure, Typed Arrays have to support the sorting API, which is comparison based and works with closures. But why not detect when someone calls sort() without any further arguments, and make that very common case use faster native code?

Because for me, this difference in performance made a difference: I am animating plots where I need to sort more than 100k elements each frame, and sort eating up 5ms or 20ms is the difference between choppy and smooth animations.

[0] https://run.perf.zone/view/radix-sort-uint32-1000-items-type...

[1] https://run.perf.zone/view/radix-sort-uint32-1000000-items-t...

[2] https://run.perf.zone/view/Radix-sort-Uint8Array-loop-vs-fil...

vanderZwan | 6 years ago

This is a broad, naive question, but the number of responses and upvotes on this post suggest to me that many people actually need to speed up their JS. I've never once come across this problem in web app development. The bottleneck is always DOM rendering like layout changes, networking, handling large WebGL vertex buffers for video games, etc. In which use cases is JS performance significant?

vortico | 6 years ago

I rely heavily on a decent JS performance baseline for http://8bitworkshop.com/, and I also rely on asm.js / WASM. But I need different things from them.

For JS, I need consistency and stability, because I'm dynamically generating code. Usually I'm pretty satisfied, but sometimes after recompiling code I get a huge performance hit for no reason.

For WASM, I know I have stability, but I need faster load times. On my Chromebook, for example, it takes 10-20 seconds just to load the WebAssembly. If this problem is solved, I might move everything performance-sensitive over to WASM eventually.

sehugg | 6 years ago

Awesome. I think it makes a lot of sense to explore new algorithmic approaches before choosing to reimplement in a new language - and thankfully these are not mutually exclusive.

To those saying "these are implementation defined optimizations" etc - you do the same exact thing in rust. I know some rust code is 'fast' and some is 'slow' and I have to understand rust and to some extent the state of llvm + rust. This is simply part of writing fast code, no matter the platform or language.

Nice writeup!

staticassertion | 6 years ago

I'm continually surprised by the Rust -> WASM pressure. I see projects like Redox and Servo and ripgrep and Tokio and, well, native or systems level things being it's true calling.

I don't want to write a webapp in Rust. It'll always be second class (though maybe that won't be a problem if the WASM apis get good enough...).

eximius | 6 years ago

Maybe there should be a tool from linters or VMs to warn against arguments adaptation, monomorphisation. For the rest it is a lot of know how is JS that is for free in with more performance minded languages. At the end with the optimized JS is is 4 times faster, but still 6 times faster in WASM it seems.

hokkos | 6 years ago

Next steps: leverage web workers and GLSL?

fulafel | 6 years ago

Irrelevant and maybe a bit small-minded, but the font of the article is too squished to be pleasantly readable.

hitekker | 6 years ago

For writing high-performance JavaScript, I'm really hoping that AssemblyScript takes off[1]. The project is still a bit in early stages, but theoretically, it can have the same role as Cython: you write JS/TS code, find the slow parts, and get perf improvements by adding stricter types to your code and changing your code to not use dynamic JS features. You can stay pretty much in JS rather than having to switch to a completely new language to get predictable fast performance.

[1] https://github.com/AssemblyScript/assemblyscript

alangpierce | 6 years ago

For me the purpose of wasm would not be just performance, but to let me avoid javascript entirely.

Javascript is already very fast, but compiling to javascript feels awkward.

jokoon | 6 years ago

I can't see any numbers comparing the rust implementation and the optimised javascript. Did I miss them in the article?

bitdivision | 6 years ago

It's not just speedup it's also about writing backend and front-end in one language. But the speedup is real too.

adamnemecek | 6 years ago

So...

I'm a (junior/so-so) react dev. I like the language, I probably could get better at it but I've gotten to the point where I like the sound of my own music.

That said, my question is the following. There seems to be a lot of resources on the web of the type "Hey Rust and WASM is a thing! You can make webpages with it". Ok, fine. However, I don't see a lot of the things that are in libraries like Vue and React that offer me SPA, fast development time and component (OR!) functional pattern design (I won't mention the ability to add npm packages, because yeah, Rust/WASM doesn't have an ecosystem yet, so that might be punching a bit below the belt). Is there anyone out there making a React like or "eco-system builder"-esque platform that would give me a lot of the benefits I'm seeing with React but with increased performance?

Also, I've done some low level programming before and think that Rust is very cool for that (yay memory safety! yay error messaging (no seriously yay)!). However, and I may be betraying my ignorance here, if I want to animate a div to fly across the page, am I going to have to write lots of low level code to do that? If that's the case, and development time suffers to eek out that extra bit of performance, I can't see this as having much utility outside of niche fields like game development.

I don't mean to be overly critical mind as I'm still (and probably will always be) a bit of a n00b. I'd love it if somebody would point me at some resources that I could burn a weekend on, if I thought the juice was worth the squeeze. I'm just not sure I know enough to know if this is something that I could be productive in (some day).

patientplatypus | 6 years ago

I’m confused as to why people think this isn’t generally applicable to JS here.

Very few of these changes are VM specific or likely to change (or any more so than WASM implementations)

- choose your algorithm carefully - make sure you’re paying attention to the data you’re applying the algorithm to is a good fit - pay attention to arity & GC pressure.

None of these are hard to do in JS & most of the VM debugging was to help identify problems in existing unoptimized code.

The rest are lessons you can take into ANY JS data processing.

abritinthebay | 6 years ago

Source maps are a debug tool. Why does the performance matter?

(And if you're shipping so much JavaScript to your site users that you need to "minify", maybe you're doing it wrong.)

Animats | 6 years ago