4 ms·
What holds me back from using WebAssembly is that it doesn't seem to provide any performance benefit for small functions. Well, not yet, I hope, because I reall
by vanderZwan 6y ago
What holds me back from using WebAssembly is that it doesn't seem to provide any performance benefit for small functions. Well, not yet, I hope, because I really like the idea of WASM.
For example, last week me and a few other users on Observable have been experimenting with various JS ports of one particular permuted congruential random number generator. Using assemblyscript I managed to write a decent-enough WASM version, and it's slower than implementing 64 bit arithmetic with four 32 bit numbers, which involves a lot more multiplications and additions:
https://observablehq.com/d/ce811886e1071d69 https://observablehq.com/d/ce811886e1071d69
There are two possible explanations: either 64 bit integer arithmetic is still really slow in WASM, or the call overhead is really, really significant.
This fits my earlier experience with trying to implement fast log approximation functions for data-viz purposes (since the rounding errors would not be visible at sub-pixel levels). They weren't much faster than the proper Math.log function (whereas in pure C or C++ that difference would be much larger).
I suspect that if this call overhead were reduced, it would be much easier to gradually add more WASM to a Web App and reap the benefits of it.
- saagarjha 6y agoCalling a short function is generally a poor idea, to be honest. You want significant execution time on either side of the bridge, not constant boundary crossing.
- vanderZwan 6y agoThe thing is that I had expectations of why the boundary crossing was so slow. I expected a no-heap WASM function that returns a simple value to be pretty fast, but as the other comment points out the "boundary" in this case might not be the WASM/JS boundary so much as the inability to inline WASM.
- stjohnswarts 6y agoNot really. Unless it's called quite often. Premature optimization is not a good thing in general. Find your bottlenecks, code for correctness and ease of understanding first and use optimization if it's basically "free" and don't worry much about it otherwise.
- qppo 6y agoFunction calls are expensive inside hot loops, that's why we rely on compilers to do inlining and unrolling optimizations for us. I don't think that's possible in that example, you should write the loop in C and return the data you expect instead of writing the loop in JS and calling tiny functions that will never be inlined. For what it's worth, (scalar) numerical operations in JS are actually quite close to C. It's not like python.
- vanderZwan 6y agoAh, I'm such an idiot for not thinking of inlining! The problem with writing the loop on the WASM side is that passing the data back and forth between JS and WASM involves typed arrays and array buffers. That has significant overhead of its own. Still, good suggestion, I should try that with a pixel buffer! > For what it's worth, (scalar) numerical operations in JS are actually quite close to C. It's not like python. Indeed, and I am generally very impressed and happy with the performance of JavaScript these days! In my experience it only takes a little bit of effort to hint at the compiler if we're working with doubles or 32 bit integers, and then it goes really fast. In this particular case however we needed 64 bit integer arithmetic, so that's a bit of an edge-case.
- flohofwoe 6y ago64-bit integer arithmetic is fast in WASM (these emulators are full of bit-twiddling on 64-bit integers: https://floooh.github.io/tiny8bit/ https://floooh.github.io/tiny8bit/). Calling overhead between WASM and JS is significant though, but it has already been optimized massively [1], I doubt that this can be improved much further). Calling a very small function across language borders is amost always a bad idea, not just in the specific JS=>WASM context. [1] https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%F0%9F%8E%89/ https://hacks.mozilla.org/2018/10/calls-between-javascript-a... PS: also contrary to popular belief, JS and WASM have similar performance for simple number crunching tasks because in that situation JS is pretty fast, Javascript only becomes slow when objects and properties come into play.
- vanderZwan 6y agoThanks for the insight and the links! Scrolling down the article I spotted this: > There’s only one case where an optimized call from JavaScript » WebAssembly is not faster than JavaScript » JavaScript. That is when JavaScript has in-lined a function. Combined with your remark that 64 bit arithmetic is fast, I'd say this pretty much confirms the suspicion that a lack of inlining is what makes this particular WASM function execute slower
- maxgraey 6y agoBtw you could speedup your AssemblyScript code: https://bit.ly/2FW2iyN https://bit.ly/2FW2iyN ``` const eps: f64 = 2.3283064365386963e-10; const m: u64 = 6364136223846793005; const inc: u64 = 1442695040888963407; let state: u64 = 1; export function random(): f64 { let oldstate = state * 6364136223846793005 + 1442695040888963407; state = oldstate; const xorshifted = u32(((oldstate >>> 18) ^ oldstate) >> 27); const out_int = rotr(xorshifted, u32(oldstate >> 59)); return eps * out_int; } ``` I recommend return f64 instead f32. This has slightly faster interop
- vanderZwan 6y agoThanks for the tips! (also, as you might have noticed, HN doesn't support MD proper. To get code blocks you have to indent it with four spaces) const eps: f64 = 2.3283064365386963e-10; const m: u64 = 6364136223846793005; const inc: u64 = 1442695040888963407; let state: u64 = 1; export function random(): f64 { let oldstate = state * 6364136223846793005 + 1442695040888963407; state = oldstate; const xorshifted = u32(((oldstate >>> 18) ^ oldstate) >> 27); const out_int = rotr(xorshifted, u32(oldstate >> 59)); return eps * out_int; }
- maxgraey 6y ago> To get code blocks you have to indent it with four spaces I'll keep that in mind in next time. Thanks for the tip!
- chrismorgan 6y ago(Technical note: two spaces of indentation is sufficient. Note also how the common leading spaces are not removed.)
- vanderZwan 6y agoAh, thank you!
- throwaway189262 6y agoAre you compiling C or Rust to WASM? In my experience their LLVM backend is the best developed. WASM shines for numeric computation like encoding, decoding, encryption. In the benchmarks I've seen, image encoding is 10-20X faster in WASM. Crypto is about the same speed as WebCrypto which isn't available in all browsers and runs native code. There's an unmistakable performance advantage for compute heavy workloads.
- vanderZwan 6y agoI know in which niches WASM already shines, my point is that I have a different one and wish I could effectively use WASM there as well.
- throwaway189262 6y agoYou could, but you would have to move most of the app into WASM. I think it's inevitable for WASM to replace JS eventually. We're just in the early days. WASM has 2-5X performance advantage for "most" workloads. It also parses much faster and is more compact than minified JS. Eventually the performance advantages will be too large to ignore
- pansa2 6y ago> I think it's inevitable for WASM to replace JS eventually. Do you think front-end developers will start writing C/C++/Rust instead of JavaScript? Or do we need other languages that can compile to WASM before it can replace JS?
- throwaway189262 6y agoI think it will be like back end, a mix of Java/C#/C++. JS isn't a great language, it's only popular because you can't use anything else on the web. The massive popularity of Typescript, even though it's only syntax sugar on top of JS, shows that the language leaves a lot to be desired. It doesn't support threading, doesn't compile, has garbage standard library. And it's slower and more memory hungry than languages with these features like C# and Java. There's talk of adding a "universal" GC to WASM. When this happens you'll see "traditional" enterprise languages invade enmasse