Is WebAssembly magic performance pixie dust?
surma.dev
surma.dev
Since unwarmed first execution is a very common use case on the web, and a very intentional improvement for WASM, it seems foolish to discard that when comparing. I understand for benchmark determinism warming is important in long-running systems, but when parsing/warming is a common part of every run, it deserves to be factored in. I can make WASM look better by removing intentional benefits from JS before comparing too.
You even included in the quote: It’s important to warm-up the code is so you don’t measure a mixture of performance characteristics between interpreted JS and compiled jS. How long the warmup takes is _incredibly_ device dependent, so instead I measured Ignition and SparkPlug independently so you get a feel for the speedup. I did not discard that at all in the comparison.
* It's not in the later ones
* You present TurboFan performance as the speed of JavaScript. e.g. your tables present JavaScript with Ignition as ~40x slower than JavaScript in certain test runs.
kodablah's point is a valid one generally. A parallel post notes that a developer can't avoid the warm-up time, but they can by using WASM.
As an aside, does v8 cache the optimized native code to any degree? I know there is code caching in the major browsers to presumably avoid reparsing Javascript, but if I had a theoretical page with say an image blur function, would each visit/load go through the same analysis/optimization process, going from slow to fast?
So, when you're trying to progressively improve performance of specific code (e.g. boost FPS) the warm up time is better ignored; it's not under your control.
You should measure the cases you actually care about. For a long running app, start-up time is probably not the most important thing, for other apps its very important.
If I'm trying to improve boot time of my node app, then I must benchmark the cold behavior, (the interpreter). If I'm judging template engine performance, I probably want to do that with a hot VM, because that's going to mostly be steady state performance I'm looking at. The major exception to that may be if I have an exceedingly aggressive caching strategy, where most pages are generated shortly after a deployment (evict all the old pages with whatever the new code generates as output).
If you're running a given piece of code only once, the interpreted code is almost guaranteed to be MUCH faster than compiling and then executing a large pile of code.
Let's say you have some trivial task. You write and compile to wasm. You also write in JS. It'll be a couple kilobytes for JS and probably a couple hundred kilobytes for wasm with all your dependencies to get a usable environment.
Once downloaded, the JS and wasm bytecode must now be parsed. The JS interpreter starts parsing and running. Meanwhile the wasm code is pre-compiling into native code so it can start execution.
The tiny bit of JS takes 10ms to run in the interpreter. The larger WASM file takes 20ms to parse and 0.1ms to run. Which was faster?
That depends on how long the software runs. WASM only makes sense at the inflection point where the execution lasts long enough to counteract the parse time. This shouldn't come as a surprise. Compile lag is one reason why interpreted languages saw real-world use in the first place.
It's telling that v8 used to NOT have an interpreter, but then added one. Everything was first converted to byte code then run. This resulted in slower startup and slower overall performance. Now, the browser starts interpreting while also converting to bytecode in the background before switching over execution. Functions don't actually need a couple hundred runs in order for the optimizer to know what types they receive. Most functions could be optimized with a very high degree of success after only a couple executions. This isn't done because the time cost of optimizing would outweigh the benefit on code that isn't executed frequently.
I thought wasm's format was specifically designed so that parsing and compiling could be performed in a streaming fashion, so that you don't have to wait for the download to finish.
My example assumed you were running the code locally. If it's coming over the wire, larger WASM binaries will suffer an additional penalty over the network because download speed is much slower than parse speed.
https://hacks.mozilla.org/2017/02/what-makes-webassembly-fas...
https://hacks.mozilla.org/2018/01/making-webassembly-even-fa...
For example, I have a hash function library (https://github.com/Daninet/hash-wasm) where I was able to archive 14x speedup at SHA-1 and 5x speedup at MD5 compared to the best JS implementations.
You can run the benchmarks on your computer here: https://daninet.github.io/hash-wasm-benchmark/
I'm more of a web developer and every time I think "hmm, could I use this to build a webapp?", but quickly shrug it off because it would create a big headache and the JS execution is rarely the bottleneck (and if it is, it's likely developer error and inefficiencies than the language / interpreter).
- The whole CPython interpreter is the "C-extension interface" which means that the CPython interpreter can hardly change or be optimized or else it will break something in the ecosystem (and for the same compatibility reason it's virtually impossible for alternative optimized interpreters to make headway), and because the interpreter is so poorly optimized the ecosystem depends on C extensions for performance. WASM presumably won't have this distinction.
- Without the abysmal build ecosystem that C and C++ projects tend to bring with them, building and deploying WASM applications will likely be pleasant and easy after a few years. Of course, if your WASM is generated from C/C++ then that's a real bummer, but fortunately this should be a much smaller fraction of the ecosystem than it is with C/Python.
If you do it right, that step only has to happen once for each user interaction. You can entirely dispense with the need to do multiple edits to the DOM via pure JS.
At the moment, the "virtual DOM" approach is actually going against performance optimization.
JS frameworks like react, vue, angualr etc effectively replicates a big portion of browser's internal logic for nothing.
The misrepresentation of a virtual DOM as a performance improvement came from two things: people who were comparing virtual DOM code to sloppy unoptimized code which was regenerating the DOM on every change and React fans not wanting to believe their new favorite was a regression in any way (not to be confused with the actual React team who certainly knew how to do real benchmarks and were quite open about limitations).
There’s a line of argument that the extra overhead is worth it if the average developer writes more efficient code than they did with other approaches but I think that’s leaving a lot of room for alternatives which don’t have that much inefficiency baked into the design.
Above all I would pragmatically argue (subjectively) that the main advantage is enabling a more functional style of programs w/ terrific state management (like Elm). This can lead to fewer errors, easier debugging, and often better performance with less effort.
The key part is remembering that every one of those techniques can be done in normal DOM as well. This is just rediscovering Amdahl's law: there is no way for <virtual DOM> + <real DOM> to be smaller than <real DOM> in the general case. React has improved since the time I found a 5 order of magnitude performance disadvantage (yes, after using keys) but the virtual DOM will always add a substantial amount of overhead to run all of that extra code and the memory footprint is similarly non-trivial.
The better argument to make is your last one, namely that React improves your average code quality and makes it easier for you to focus on the algorithmic improvements which are probably more significant in many applications and could be harder depending on the style. For example, maybe on a large application you found that you were thrashing the DOM because different components were triggering update/measure/update/measure cycles forcing recalculation and switching to React was easier than using fastdom-style techniques to avoid that. Or simply that while it's easy to beat React's performance you found that your team saw enough additional bugs managing things like DOM references that the developer productivity was worth a modest performance impact. Those are all reasonable conclusions but it's important not to forget that there is a tradeoff being made and periodically assess whether you still agree with it.
There are two separate issues here: one is how well you can avoid updating things which didn't change — for example, at one point I had a big table showing progress for a number of asynchronous operations (hashing + chunked uploads) and the approach I used was saving the appropriate td element in scope so the JavaScript was just doing elem.innerText = x, which is faster than anything which involves regenerating the DOM or updating any other property which the update didn't affect.
The other is how well you can order updates — the DOM doesn't have a batch update concept but what is really critical is not interleaving updates with DOM calls which require it to calculate the layout (e.g. measuring the width or height of an element which depends on what you just updated). You don't necessarily need to batch the updates together logically as long as those reads happen after the updates are completed. A virtual DOM can make that easy but there are other options for queuing them and perhaps doing something like tossing updates into a queue which something like requestAnimationFrame triggers.
The question is whether the abstraction is worth the potential savings in complexity (which maybe is not the case, but I sure do love coding in Elm).
There isn't a right answer here: it's always going to be a balance of the kind of work you do, the size and comfort zones of your team, and your user community.
Does WASM really make sense for something that isnt constantly doing high performance calculations? Do I gain anything from using it in most SPA's?
Browser vendors have done a lot of work on that over the past decade or so. It's nowhere near as slow as it was in the early days.
Not always; I have definitely run into applications where parsing large amounts of data in code is a bottleneck, especially when building large charts. But often.
1. https://hacks.mozilla.org/2018/10/calls-between-javascript-a...
I had a PoC where my server runs Rust, exposes a JSON Rest API using serde to serialize my Rust structs to JSON. On the web Client I compiled Rust to wasm and used the Reqwest crate (http client that uses Fetch in wasm) to talk to my server, Rust structs are shared between server and client.
For me, the beauty about Rust in this setup, is that cross compiling/crossplatform is builtin into the tooling (Cargo). For example the Reqwest crate compiles down to use the browser Fetch api when running in Wasm, and the same crate on the server uses a native implementation using openssl (or rusttls).
(In practice the validation is probably not necessary but doesn't hurt to have).
https://github.com/FriendlyCaptcha/friendly-pow https://github.com/hugomrdias/rabin-wasm
> As a result, last year the V8 team issued a hardening patch designed to prevent attackers from abusing bounds check elimination. Instead of removing the checks, the compiler started marking them as “aborting”
But this post, which also appears to be written by someone from google with access to v8 developers, states that :
> You never go out of bounds. This means TurboFan does not need to emit bounds checks [...]
Does someone here know more about bound checks eliminations in TurboFan ? Are they removed in some cases but not others ?
[1] https://googleprojectzero.blogspot.com/2021/01/in-wild-serie...
In all seriousness, this is a great read, and I was mildly surprised JS was about the same speed as Wasm once TurboFan kicks in. As a compiler engineer, it's nice step back and appreciate the myriad of runtime-based optimizations that can be done with modern JIT compilers.
That being said, assembly script doesn't perform and high-level optimizations upon compilation, so I wonder how fast it will be once fully matured.
IIRC it does because it uses binaryen[0] (as claimed in the link) which is an optimizing compiler.
It’s a trade-off for simplicity. It’s a small team and they are still working towards feature completeness. Deferring optimization to Binaryen is the easy way out at the cost of not having high-level optimizations. If they finish their IR, that will most likely change.
E.g., if you choose x -> x(1+1/log(x)) then you get an amortized append time of O(log(n)) while paying a memory overhead approaching 0% for large datasets.
The distribution of (and SLOs for) appends relative to other operations can make that kind of idea more or less attractive, but even common data structures have a lot of room for improvement if you can tailor them to your use case a little bit.
Of course it depends on the behaviour and binning (or lack thereof) of your allocator.
Edit: it’s 1.5, any growth factor below 2 has this property, how far below 2 regulates how quickly it happens, https://github.com/facebook/folly/blob/master/folly/docs/FBV...
> it can be mathematically proven that a growth factor of 2 is rigorously the worst possible because it never allows the vector to reuse any of its previously-allocated memory.
> [...]
> choosing 1.5 as the factor allows memory reuse after 4 reallocations; 1.45 allows memory reuse after 3 reallocations; and 1.3 allows reuse after only 2 reallocations
Eg Python has put a lot of thought into their dynamic array (and dict) growth factors.
I hope it is just an oversight, but come on...
One of the prime features of assembly language is that the person (compiler) that is generating it expects tight control over what it does. A 2*X allocation when you ask for X is unexpected.
Imagine if, when you went to the ATM and withdrew $100, the bank actually withdrew $200 from your account and held back the extra $100 so that, the next time you went to the bank and withdrew $20 it would take it out of the "held back" amount rather than doing another withdraw. I would be very unhappy with that algorithm.
std::vector in C++ does it, so does Vec in Rust, and they are not dynamic languages with GC.
ISO C++ places no such requirement on std::vector, each implementation is free to choose their own implementation provided it matches the O() notation requirements.
C++ is not like Rust where the implementation dictates the semantics.
If the capacity is only increased by some constant each time, rather than multiplied, this leads to O(n^2) running time for a sequence of n append-to-back operations, surely something to be avoided.
There is no sensible usage for a method that does that. It turns `for(let i = 0; i < n; i++) { arr.push(x); }` from linear into quadratic.
If automatic resizing exists, then it should do it in a sensible way. Otherwise it's just a footgun that you should leave out of the language like C does.
Debugging Optimized Code with Dynamic Deoptimization
By Urs Hölzle (Stanford University), Craig Chambers (University of Washington) and David Ungar (Sun Microsystems Labs).
https://bibliography.selflanguage.org/_static/dynamic-deopti...
>Abstract: Self’s debugging system provides complete source-level debugging (expected behavior) with globally optimized code. It shields the debugger from optimizations performed by the compiler by dynamically deoptimizing code on demand. Deoptimization only affects the procedure activations that are actively being debugged; all other code runs at full speed. Deoptimization requires the compiler to supply debugging information at discrete interrupt points; the compiler can still perform extensive optimizations between interrupt points without affecting debuggability. At the same time, the inability to interrupt between interrupt points is invisible to the user. Our debugging system also handles programming changes during debugging. Again, the system provides expected behavior: it is possible to change a running program and immediately observe the effects of the change. Dynamic deoptimization transforms old compiled code (which may contain inlined copies of the old version of the changed procedure) into new versions reflecting the current source-level state. To the best of our knowledge, Self is the first practical system providing full expected behavior with globally optimized code.
>Proceedings of the ACM SIGPLAN ‘92 Conference on Programming Language Design and Implementation, pp. 32-43, San Francisco, June, 1992.
edit: Just saw
> Web Advocate @Google.
The advantage of using v8 (d8) here is that the author could benchmark different optimization strategies. I don't think jsc lets you do that.
WASM binary size is highly dependent on optimization level. If your WASM is anywhere near the size of your JS, something is off with your compiler settings. Since Rust and C++ are statically typed, compilers can use LTO to remove almost every unreachable instruction. JS, even with tree shaking, gets nowhere near this.
Default settings in Emscipten generate huge binaries. Rust without the "native" target or a bunch of configuration also does.
WASM bytecode is also more compact than JS. There's simply no reason the binaries should be even close to the same size.
The memory usage should also be different by and order of magnitude. WASM has basically no overhead above machine sizes for most types. Look at Benchmarks Game, memory use JS vs Rust. You should be seeing a difference close to that.
I can't speak for speed. But I can say I did a head-to-head comparison between the fastest pure JS PNG encoder I could find vs a C encoder transpiled to WASM, and the transpiled encoder was >10X faster.
It's hard to say if something is off in the benchmark or compilation but I find it hard to believe there's such a big difference between my tests and yours. Emcripten especially is not exactly easy to use and maybe a good place to look for size and speed optimization
The author does explain this pretty well. For an exact 1:1 comparison, yes WASM beats JS for size. JS comes with built in functionality (e.g. a garbage collector) that doesn't cost any size, but in the WASM case needs to be brought along taking up space. Even if you don't want GC, you don't get any WASM 'standard library'.
Instead we got the most bare-bone language imaginable, making everyone have to reinvent the wheel for everything. That is not actually fast. If WebAssembly had stuff like basic string manipulation, browsers could easily map that directly to efficient implementations. But with the current rules everything has to be provided as basic instructions that must be compiled under much stricter security rules.
WebAssembly is not assembly, it is an intermediate representation shared between two compilers, and it is actually pretty bad at that.
WASM is an exceptionally good standard compared to most other things on the web platform.
The thing about WASM performance is that WASM is fast (unless you do stupid things), but what's surprising to most people is that Javascript isn't slow either (at an absurdly high engineering and complexity cost compared to WASM though).
I'd argue that the main point of WASM is not the performance gain, but that it opens up a fairly straightforward path to use different languages on the web (e.g. it was possible to upstream a WASM backend into LLVM, but if the Emscripten team would have tried to upstream an asm.js backend into LLVM, they'd be laughed out of the room I'm sure - and asm.js also wasn't fast without special handling by the Javascript engine either).
Also don't forget that the above blog post is mostly about "WASM isn't as fast as it should be when using AssemblyScript", which is more of a problem to solve for AssemblyScript than WASM, because when used from C it's fairly easy to get "near-native" performance.
PS: all the disadvantages you're listing (like the linear memory layout) are actually massive advantages (for instance when trying to optimize cache misses) ;)
This isn't WebAssembly being slow the benchmarks just show the overhead of the GC. If you are writing a computationally expensive algorithm in Rust or C++ wasm can be a lot faster but its hard to get close to native performance(i.e. running on bare metal x86/arm).
Our webassembly prototype is about 3-4x faster in raw computation(and that is targeting webassembly exclusively) but has a much higher overhead when interacting with the DOM. That is basically the limiting factor especially on mobile.
> I want to be very clear: Any generalized, quantitative take-away from this article would be ill-advised.
I don't think WASM and JS are "just as fast"--the author only did a couple of microbenchmarks. There are almost certainly many cases in which WASM would outperform JS, but they probably aren't going to be tight loops over an array or similar.
b) WASM is faster today, if done right
c) be faster with WASM with ease ... in the future, after it all and mostly tooling for it, gets stable
The compiler in the browser is going to run its own optimization anyway, preoptimizing the input isn't necessarily going to help.
Additionally, I think optimized webassembly should normally be a big benefit, helping both startup time and optimizations the engine might miss (also helping the engine focus on other optimizations / making simpler engines performant).
edit: Indeed, optimization before webassembly makes a big difference in the article's benchmarks, as you can see with how C++/Rust was faster than the hand-crafted AssemblyScript, theoretically because C++/Rust is going through LLVM optimizations.
The web used to be quite simple but now it sometimes feels like you have to have a very large team in order to do anything. Sure, I could probably do web assembly but that would take time from other things that also is important.
I get that it is very useful for larger teams making larger applications like Figma. But for small teams it feels like the tooling isn't there yet to do anything useful in the timespan that I need to.
That being said, assemblyscript seems very interesting and I probably should look into it.
You don’t really need to learn the assembly language itself since you’ll probably just be calling emcc.
However, you may need to build code to marshall more than just ints and strings to the JS code. Even after you do, you’ll run into the classical issues of keeping track of object references across a GC & non-GC system.
You may need debugging and find that in-browser debuggers for WASM are primitive/non-existent. You may need to figure out how unmangle stack traces —- including mixed JS/WASM traces. Third-party tools like Sentry for error reporting may not have built support (they sort of recently have and is very under-documented).
All solvable problems, but it’s a lot of time spent not building the product. There are plenty of good uses cases but it’s usually not the ones based on the false premise that native is somehow always better than interpreted.
The way I structure learning things like this in my team is by organizing spikes. We commit to diving into some technology with the goal of finding out how feasible it is for us to use it and a secondary goal of maybe getting something useful going. However the primary goal is finding out if it can work and if so exactly how. If it works out it becomes a regular thing we work on and integrate. This usually starts with studying what is there, what the risks and benefits are, etc.
At some point you reach the point where the only way to learn more is simply doing it. You can analyze something to death without fully understanding it. Just sitting down and doing it becomes the logical next step. The payoff is usually non linear: you gain more if it works than you lose if it doesn't. This is one of those things that you might suspect is valuable like that. So your job is finding that out in an efficient way.
In your team, I would task one or two of your people with spending a max of 2 days to validate that they can take a simple bit of typescript, convert it to assembly script, compile it and hook it up. Chances are pretty good that they'll have working code at the end of those two days. Worst case you lose two days. Best case you figure out it's easy and just works and you move forward.
Using ASC was intentional, _because_ it is somewhat immature, and still manages to outperform JavaScript in the first two cases.
In the third case, where I couldn’t get ASC to outperform JavaScript, I tried Rust and C++ as well.
I found similarly "meh" performance improvements when trying to port my Javascript code to webassembly: either modern Javascript implementations are absolutely amazing, or webassembly runtimes still have a ways to go.
Typed arrays are mainly faster in scenarios where you allocate one or a few large TypedArrays once and then re-use them a lot.
That also explains your experience with many small arrays. That's basically the worst way to use TypedArrays.
let a = 1; let b = 2; let c = a+b|0; // c is guaranteed to be a 32 bit signed integer
This not only ensures correct 32 bit integer semantics (like wrapping around), but also helps the engines to use actual integer instructions in the generated machine code.
For unsigned 32 bit integers, there is `>>>0`, and for multiplication, there is Math.imul().
But there is only one type of number in javascript. Everything is just a double(BigInt aside). You get a 32-bit integer because the bitwise operator casts the result to one. c has the exact same semantics as any other js number.
Yeah there are tricks to convince the engine you are using an integral type but those unless you are doing a lot of benchmarks they aren't really useful. Any compilation tier can choose to use any intermediate representation it wants.
There is nothing stopping JS engines from trying to infer when a number is only an integer and optimize for that. In fact, that's what Small Integer (SMI) optimizations are all about[0].
It's just that |0 isn't really able to guarantee that our number value is the type of SMI that V8 optimizes for (since the V8 SMIs are 31 bits, and bitmasking operations only guarantee 32 bit integers)
https://ponyfoo.com/articles/an-introduction-to-speculative-...
For any of those optimizations to happen for your WASM code, the compiler has to be able to do it statically and that can be much harder. Devirtualization in particular is essential for Java or C# to run fast and some C++ codebases also benefit tremendously from it. If you're interacting a lot with JS APIs from WASM (like issuing network requests or creating DOM elements, etc) you're going to be dealing with lots of dynamically typed data, and in those scenarios handwritten JS may actually be faster than WASM because the runtime can JIT optimal code with the right type specializations.
Note that these optimizations will fail if you aren't careful about how you write your JS: If a given function f(x,y) is passed values of different types during execution, it probably won't be fully optimized. If you have two functions f1(x,y) and f2(x,y) and ensure that each one is only passed values of a certain type, they will both be heavily optimized (iirc the JS runtime terminology for these functions is 'monomorphic') Naturally, this means uses of Function.apply and Function.call should be avoided at all costs.
However, that has not planned out.
There are a few reasons for this. First C++ and Rust optimizers can do amazing things when they are given time. In addition, I think devirtualization is not as big a deal in C++ and Rust because in general you avoid writing code that uses virtual functions when you are writing performance sensitive code and instead use things like templates/generics where there is no indirect function calls.
This wasn't tiny CLI that occasionally lands on HN, rather large scale desktop applications or distributed computing clusters.
Winning micro-benchmarks is not everything, which is why except for Windows with WinUI (which still remains to be seen if it can move windevs away from Forms/WPF in its current incomplete state), all OS vendors are migrating to other languages for their App development SDKs, leaving C++ and Rust only for low level OS components.
I don't think C++ and Rust are for just low-level. I've build a lot of GUI apps and distributed ones with C++
QT is a beast.
The issue with Java is the reverse engineering. Back in 90's, The main selling point for Java was to prevent it, because at that time the bytecode was hard to understand at least. Now tools has grown and it's fairly easy to reverse engineer Java, even if one obfuscate it.
As for C++, Inline code and template code make it pain in ass.
I'm sure big companies that care about intellectual property would use C++ over Java anytime. C++ also has mature obfuscation tools that make it even more difficult.
Java has it's place. It's great language if you use in server side or isolated env (From commercial viewpoint).
Nevertheless, I've built many web apps using C++ too.
Reverse engineering is never an issue with Java if one actually uses the right tooling, commercial AOT compilers exisst since around 2000, it is a matter of buying them.
I assume JetBrains and Google are big companies.
AOT compilers exists for Java, Net, Javascript. I however doubt the user experience of those.
For example, GraalVM mentions the following,
"There is a small portion of Java features are not susceptible to ahead-of-time compilation, and will therefore miss out on the performance advantages. To be able to build a highly optimized native executable, GraalVM runs an aggressive static analysis that requires a closed-world assumption, which means that all classes and all bytecodes that are reachable at run time must be known at build time. Therefore, it is not possible to load new data that have not been available during ahead-of-time compilation."
GraalVM is not what I would pick for AOT Java projects, there are other products since 2000.
In any case, this isn't a comparisasion of language bullet points.
Just because a software product has been migrated from C++ into Java, .NET or whatever language, it doesn't mean it is a sacrilege to keep some native lib around, which is exactly where all mainstream OSes are going, with C++ being left for the bottom layers.
How many desktop GUIs is Google shipping written in pure C++?
See list of companies using C++ QT for GUI,
2 - Qt has been migrating away from pure C++, again you also missed pure from my comment, modern Qt applications are written in Qt Quick, a JavaScript dialect, with underlying components written in C++.
C++ Widgets have hardly changed since Qt 4, other than being updated to the underlying Qt infrastructure.
You mean apart from the Chrome browser?
Were your customers enterprises that made their employees use those products, or were they end user, consumer products?
In my experience, enterprises are happy to push slow, laggy, hard to use corporate tools on their employees as long as it saves them money.
Many devs are too religious arguing for home team and don't embrace polyglot programming.
Just because a product is mainly written in managed language X, doesn't mean some library can't be written in something else.
C and C++ devs have forgotten the days when their beloved programs in 8 and 16 bit home computers, were a pile of inline Assembly if performance was to be anywhere of an acceptable level.
Embrace the safety and productivity of higher level languages (with AOT and JIT compilers), and let a couple of native libs be the "inline Assembly" if and only if, a profiler proves it is actually required instead of choosing a better data structure or algorithm.
This is different than "Java will be faster than C++ because of HotSpot" arguments, because java is competing with C++. This is not a competition between JS and native C++, it's a competition between JS and WASM.
You just need to know your tools, that's it. Plenty people forget -O flag and existence of libraries like Folly.
If you combine PGO along with these must knows, you seriously will become much more faster.
To optimize your program in a low level language you have to basically have a whole plan for the architecture of your program beforehand, and every major change to that will break your optimizations. Also, don't forget about non-standard object life cycles, which is really common. Complex C++ programs basically employ their own GCs, which will be inferior to any one included in the JVM.
Of course low-level programs have their place (plenty of), eg. audio processing, embedded, million other, but the average business/CRUD app will be faster* both to execute and to produce in Java, as well as better maintainable.
* With enough time a competent team could of course write a faster version of it in C++, but it's not a good use of their time, and you would be surprised how hard it, especially with ever-changing requirements.
I bench-marked using Intel vTune.
for loop is interesting. It's why Tensorflow Core written in C++ instead Java.
I don't know any complex C++ program that employ their own GCs when C++ has RAII which is superior to GC.
Just give a try for C++11/14/17 and you will see which one is more maintainable and expressive.
Look at Chromium codebase. It's the most beautiful codebase I've ever been to.
I've done a lot of CRUD web apps in C++ using expresscpp [1] and I would say it's easy as ABC.
A lot of Java folks haven't tried C++11/14/17 (Modern C++).
C++ is Zen of OOP.
A language either cares about low level details or not. You can’t have it both ways. And c++ is absolutely a low level language.
> I don't know any complex C++ program that employ their own GCs when C++ has RAII which is superior to GC.
RAII is not at all a replacement for GC. It is only suitable for a subset of object lifetimes. There are plenty of cases where you can’t really pinpoint a scope-exit where this given object should be reclaimed.
A GC is a necessity in many concurrent algorithms that simply could not be written without.
> Just give a try for C++11/14/17
I have and I like it. There are domains where I would not even start writing Java, and vice versa with C++.
Your CRUD app may have been a breeze but what if the requirement has changed now touching on a core of your program. You have to refactor and it will be really expensive, compared to a high level language. Every memory allocation/deallocation have to be thought out again and tested (and while rust can warn about it, you still have to write a major refactor as it is another low level lang)
Please tell me why you can't. C++ is both not one. It's a multi paradigm language.
In Modern C++, the low level details invisible.
> Every memory allocation/deallocation have to be thought out again and tested
True If you're writing C with Classes or Java Style C++.
>> C with Classes >>> malloc()
>> Java Style C++ >>> new and delete everywhere
> There are plenty of cases where you can’t really pinpoint a scope-exit where this given object should be reclaimed.
Show me. I'd bet your case can be solved with xvalues.
> A GC is a necessity in many concurrent algorithms that simply could not be written without.
Show me a concurrent algorithm that needs GC.
> but what if the requirement has changed now touching on a core of your program.
C++ is a OOP language just like Java. You do it same way as you do in Java. Use inheritance.
> major refactor as it is another low level lang
No. It's not a low level language if you write Modern C++.
The case for Java very clear prior 2011 but now C++ has caught up.
Being multi-paradigm is a different axis all around. Low-level (which is by the way not a well-defined concept, C is actually also high level, only assembly is low, but that usage is not that useful) means that low level details leak into your high level description of code, making the two coupled. You can’t make them invisible.
Also, as an example, think of Qt. A widget’s lifetime is absolutely not scope-based, nor is it living throughout the whole program. You have to explicitly destruct it somewhere. And there are plenty of other examples.
And as I said, I’m familiar with RAII, it’s really great when the given object is scope-based, but can’t do anything otherwise.
> C++ is a OOP language just like Java. You do it same way as you do in Java. Use inheritance.
And if the new subclass has some non-standard object life cycle you HAVE to handle that case somewhere else, modifying another aspect of the code. It is not invisible, unless you want leaking code/memory corruption.
It's your job to make it not to leak. You have to write Modern C++ wrappers around C libs.
Similarity, The same can be said for Java. You can do low level in Java.
C++ is not C. C++ has backward compatibility with C.
Look at Boost folks, they wrote a Modern C++ wrapper around a C HTTP parser.
> And as I said, I’m familiar with RAII, it’s really great when the given object is scope-based, but can’t do anything otherwise.
Nothing is impossible.
You can use Scope Exit Guard with QT Widget.
https://github.com/ricab/scope_guard
> And if the new subclass has some non-standard object life cycle you HAVE to handle that case somewhere else, modifying another aspect of the code. It is not invisible, unless you want leaking code/memory corruption.
Again, Scope Exit Guards solve your problem!
I would expect a JIT to not really be able to find a lot of magic optimization opportunities, though maybe there are some, and it'd actually be annoying if it could. The most important thing in a tool like that is predictability, because you can't make development decisions based on magic.
That may be part of it, but I imagine the JVM's safety obligations are also a significant factor. If the JIT can't elide array bounds checks, checks must be performed at runtime. Runtime type checks might be needed. Runtime arithmetic checks might also be needed. The JVM is also more constraining regarding concurrency gone awry, than the C/C++ memory model. [0] More broadly, the JVM's lack of undefined behaviour constrains the optimiser in ways the C/C++ approach does not (although I'm open to the idea that it's overstated how much of a performance win is owed to C and C++ having many kinds of undefined behaviour).
And of course there's the GC and Java's high object-churn, even where lifetimes are known statically. To my knowledge, escape analysis (the relevant family of JIT optimisations) still hasn't really addressed this.
And you are right in that many Java libs/programs are quite happy to create garbage, though with generational GCs it is really cheap. Escape analysis is great, but primitive classes in Project Valhalla will solve this last problem of object locality.
There's still a lot of magic you can't rely on, but you'd be surprised how much you CAN rely on. Asm.js was built on this observation: If you write your JS following some basic rules it's actually pretty easy to land on predictable, well-optimized paths. Of course, one of WASM's advantages is that by design you're almost always on those paths and don't have to worry.
Fortunately you've got the best profiling tools available, so you don't have to guess. And also you get to see the relative importance of the function you try to optimize, whether that actually is the bottleneck (and actually people often guess wrongly where the bottleneck is)
TS classes are not JS classes, TS has it's own implementation of async/await, etc. Just check any compiled TS code and you'll see it. It's very frustrating when you want to quickly patch a bug in 3rd-party library.
To do TS on JS you'd have to build a VM on top of WA.
And that would probably be pointless of course.
Yes of course WASM is a VM of some kind, that should be obvious enough i.e. people are not running machine code in WASM.
My response indicated that if you want to run JS on WASM you have to build another VM on top of WASM (which you indicated is already a VM, sure).
To do JS in WASM you'd have to build something like V8 (a VM) on top of WASM.
"MakeCode Languages: Blocks, Static TypeScript and Static Python"
It is very hard to generate good AOT code when dealing with dynamic languages, of any kind.
But they are also working on more efficient bindings.
When WASM got released (around 2017), there was already that discussion to allow direct bindings without a JS roundtrip, but AFAIK there is still no actual implementation for this in any browser.
The calling overhead between WASM and JS is quite negligable compared to that (at least since around 2018: https://hacks.mozilla.org/2018/10/calls-between-javascript-a...).
Another problem is that WebGL relies on garbage collected Javascript objects, but this problem can't really be solved on the WASM side, even with the "anyref" proposal (this would just allow to remove the mapping layer between integer ids and Javascript objects that's currently needed).
I guess it depends on what you are doing. For most people doing web assembly, the point is avoiding dealing with/minimizing the need for interacting with javascript. But still, it seems there are some nice virtual dom options for Rust: https://github.com/fitzgen/dodrio that are allededly fast and performant (not a Rust programmer myself).
[0] https://en.wikipedia.org/wiki/Lars_Bak_(computer_programmer)
So most (all?) of the code posted which looked like a straight port to AssemblyScript was slower than JavaScript before optimizing it? I don‘t know how you feel, but i personally don‘t want to optimize every function to get the promised speed :(
If I could just replace my slowest Javascript function with handcrafted WebAssembly code, that would be great.
But having to dabble with external compilers and splitting my code into multiple files is too much of a burden.
This should be possible:
<script src="some.url/gopherjs.js">
<script type="application/golang">
package main
import "fmt"
func main() {
fmt.Printf("yeah baby\n") // Effectively console.log
}
</script>
Obviously this would take more than a bit of time to start up (seconds), but the idea is of course that you don't do this once you deploy to production, and replace by inline webassembly.What is "inline webassembly"?
WebAssembly.instantiate(new Uint8Array([0,97,115,109,1,0,0,0,....], { type: 'application/wasm' });
Inline webassembly = directly specifying the wasm binary inside of the html or javascript file.In my work, I have come to conclusion that it seldom pays off to go "native" when working with Node.js. More often than not, rewriting some computationally heavy code in C and sticking it as a native module yielded marginally better results when compared with properly optimized js code. Though, that doesn't negate other advantages of using said technologies: predictable performance from the start and re-using existing code base.
It has forks, revisions, probably some tooling built around (git -> gist) but it's not indexed and can only be found by finding the link somewhere (in most cases).
Is this a more wide-spread recent pattern? Wondering what's the desired outcome in how it compares to just a public repo.
They are written in languages with incompatible memory models, so lots of data must be copied when communication. They are running in different runtimes, so your javascript JIt can not inline function calls into the DOM.
That's why to this day, if you want to render a bunch of html from javascript, it is faster to generate a giant strin g of markup and pass that to the browser in a single 'innerHTML = "foo"' and let the browser parse all that, than it is to call a bunch of "createElement(); setAttribute(); appendChild();" calls.
It used to be the case a number of years ago when I last benchmarked.
https://gist.github.com/adamvy/afcace8cbdbe56995626f59f6ea2b...
Load this as a script tag in an html file to run.
Last time I benchmarked this it was true, but that was a number of years ago.
At least in firefox here.
Benchmarking is difficult.
If the issue is warmup time, do there not exist AOT compilers for JS (or TS) to WASM?
I hope WASM can continue to grow in both of those areas, cause I still like the idea but it's clearly still an immature technology.
So, it's more useful to talk about, let's say, v8 performance. Much of v8 performance is also version specific, and not all of it is documented.
Code running on v8 can be blazing fast, or it can be slow. It depends on whether the code can be "optimized" (JIT compiled) and kept that way (because code can also be "deoptimized", meaning, the jitted version gets thrown away).
With JavaScript, if you want to ensure your code always runs fast as it possibly can, you have to become acquainted with the rules behind optimization and deoptimization, and start tracing optimizations and deoptimizations to make sure that your code gets optimized, and stays optimized. This process can be time consuming and can make your JavaScript look non-idiomatic and less readable.
In the other hand, WebAssembly performance is easier to reason about with respect to what's described above.
I see no mention of his port of blur functionality where he tested to see the results of original JavaScript blurring algorithm be the same of the porting one. And believe me, image manipulation can bite you in the proverbial rear at edge cases the best, I've been there. What I want is to see also testing included in the article, not just bench-marking his own solution. Try testing at least the classic 256 cases, that's RGB(x,x,x) (examples: RGB(0,0,0)-black...RGB(127, 127, 127)-gray...RGB(255, 255, 255)-white) then a few thousand random images. Only after that test you can safely move to benchmark for the speed.
* reference types * interface types * exception handling * typed function references * garbage collection
I.e. most WASM proposals must be accepted before we can use the DOM from WASM... by the pace it's been moving forward, I guess this will take at least several years (3-4+).
That’s great from a trust perspective because it means you can use a binary blob with confidence that it can’t do anything it isn't explicitly allowed to.
There’s no reason to specify access to the DOM for webassembly since you can grant that access from JS.
The hard bit is making it fast; ideally you could call between WASM and browser code with zero trampolines, but going via JS means you need two.
There are reasons. If I could write web apps in a different language without having to use any JS, I would. It would be wonderful to be able to pick whatever language you want, compile it, and then deploy it. Having a JS bridge just seems like a clunky workaround that you have to live with.
> That’s great from a trust perspective because it means you can use a binary blob with confidence that it can’t do anything it isn't explicitly allowed to.
JavaScript is already sandboxed and can access only what you allow it to. Why have a sandbox within a sandbox?
It's 100 lines of code that you write once and never touch again. Not especially hard to live with VS having a fixed spec that can't evolve over time.
> JavaScript is already sandboxed and can access only what you allow it to. Why have a sandbox within a sandbox?
Javascript can access only what the user grants it access to. Within that set, WebAssembly can access only what the site author grants it access to.
There are quite a few good reasons to want nesting sandboxes. Hell, that's my favorite thing about Lua (a language I would otherwise avoid).
The idea seems that WASM tries to be a general compilation target, so they don't wanna mess it up by integrating with the web platform too soon.
Don't know when or if the interface types are released.
Anything that needs to do a lot of DOM access will probably see a big performance hit if you rewrite it in WASM because there will be too much overhead from crossing the JS-WASM border.
Isn't this a security risk?
Imagine an HN-like page is written in this and uses unchecked in some code path for displaying user comments.
I comment something that exploits that code. Then when you come along and view my comment. Now whatever my exploit does is running with your permissions instead of mine.
Much like exploiting a C program that handles untrusted text and doesn't bounds check it. You aren't supposed to be able to run any code at all, but a vulnerability lets you make the program do something it isn't supposed to.
Some of the easiest exploits would be prevented since you probably can't overwrite code like you can (or used to be able to, a lot of platforms have added protection against this) in C, but some exploits are still possible just by overwriting other variables with values that the program doesn't expect.
I tried brython, and it's very very slow.
I imagine it would make more sense to compile Python to JavaScript and leverage the optimising JavaScript JIT engines, but no doubt an efficient transpiler would be a significant undertaking. The Transcrypt project [0] does something like this but I don't think it emphasises performance.
I tried several time and always got stuck somewhere.
Rust that has the reputation to be hard, but is way simpler and faster to get into WebAssembly.
I wish they adopt nlvm as the default compiler, it would make stuff like that way easier and probably boost adoption.
The reason it's easier in Rust is because someone that's passionate about WASM came along and did the work up front to document everything and make it as simple to compile to WASM as possible, the same is certainly possible in Nim, just need someone to put in some work :)
I really do like Nim, but suggesting it to do WebAssembly out of the box could be deceiving and do no good. The project is currently in the hard spot where it's hard to convince your team of using it, but because its hard they is less contribution.
As for llvm, you might be right, I'm not an expert in that particular field. But it seems there is a lot of already backed in stuff you'll have for free. Like Wasm, debugging, optimization. We'll see how the Crystal project manage to get Wasm and how llvm will help them.
Just stop festooning a site with pop-ups, trackers, geolocation, subscriptions and all the countless other crap that's increasingly being shoved into web pages. The problem will go away by itself.
Decide for yourself, whether this article is worth your time.
Did you even read the article? It goes into a deep dive on AssemblyScript and WASM including discussions on Rust’s std::vec and Go’s slices, bump allocators, TurboFan/Sparkplug/Ignition/Liftoff benchmarks, and -O3 versus -O3s flags and contains links to a pull request to try to help with one of the noticed performance issues.
This is a well-written dive into the technology, writing it off because you don't like the definition of "strongly typed" in this sentence is a bit premature.
And indeed, this article does seem to be worth my time.
I wasn’t confused by that statement when reading the article. Obviously wasm isn’t strongly typed but obviously that’s not literally what the author meant.
The article was interesting and they clearly spent a substantial amount of time creating it.
Is this maybe something with academic definition of strongly typed?
Because the way I understand Webassembly, it surely is strongly typed.
edit:
"Strongly typed is a concept used to refer to a programming language that enforces strict restrictions on intermixing of values with differing data types."
https://www.techopedia.com/definition/24434/strongly-typed
Seems to back me up.
either you don't understand type systems, or you don't understand WebAssembly. in either case, I can encourage reading the article, because it sheds some light on both topics!