In zhihu’s “What do you think about WebAssembly technology” question, you can see that there are many common misconceptions about the triangle between browsers, WASM, and JS. So here, as a bai xue (JIA), I try to correct some common problems.

Abstract: The WASM runtime performance is limited in principle, and even JS can compete with Rust, which compiles to WASM. In addition, toolchain is highly invasive, so it is not suitable for all in, but it has great potential for cross-platform distribution of native applications.

WASM == assembler level performance?

This is obviously not true, because an Assembly in WASM does not mean the actual Assembly code, but rather the bytecode of a new convention that requires the interpreter to run. This interpreter is certainly much faster than a JS interpreter, but certainly not as good as true native machine code. As a reference, the overall performance of JS with the JIT is about 1/20 of machine code, while WASM can run up to 1/3 of machine code. Even if you’re writing C++ and Rust, you’re only getting Java and C# performance. This also explains why WASM doesn’t show overwhelming performance advantages in all applications: As long as you know how to keep the JS engine on the Happy Path, JS can compete 50-50 with Rust in the browser.

A classic example of performance comparisons between WASM and JS is the Mozilla developers and V8 developers’ study site. The process goes like this:

  • Mozilla Hacks published an article calledOptimize Source Map performance with Rust and WASMBlog post, willsource-mapThe performance of this JS packageIt’s optimized five times.
  • V8 core developer Vyacheslav Egorov responded to a blog post titled “You may not need to optimize JS with Rust and WASM,” by achieving amazing optimizations with pure JS that are faster than Rust.
  • The author takes the discussion further in the name of speed without magic and uses Rust to make new performance optimizations.

Coincidentally, the controversy took place two years ago during the white album season. The two sides played a high-level match just like Snow choi and Dong Ma. Finally, Vyacheslav presented a performance comparison diagram after three rounds of moves. It can be seen that although Rust was the faster dog in the end, JS was pushed to the limit and won a round instead of losing:

JS: You are the first to start performance tuning! Are you the one who goes where I can’t reach? You’re the one who came up with the idea of torture that’s so far away, yet so close! Obviously so, why still have to be blamed by you can’t ah… ? Like that… Every day, every day, before my eyes, running so fast… And that it was all my fault… How cruel…

In addition, Milo Yip’s extensive ray-tracing performance tests in different languages (Shura Field) can also confirm the performance comparison conclusion between VM language and machine code. C++, Java, and JS can represent three typical performance levels without specific optimization:

C++/C#/F#/Java/JS/Lua/Python/Ruby render comparison

Ruby: Why are you all so skilled?

WASM is faster than JS, so computation-intensive applications should use it?

This is a bit biased, WASM is also CPU computing. For tasks that can be highly parallelized, it is often faster to use WebGL for GPU acceleration. For example, the image processing algorithm I introduced in the introduction to Practical WebGL image Processing can be easily dozens of times faster than the for loop traversing Canvas pixels in JS. The drudgery of a two-layer for loop can be rewritten several times faster with current WASM. As for the performance of in-browser AI computing, the community also rated WebGL and WebMetal as having the highest level of performance, followed by WASM. See here: AI profiling in the browser

However, there are precision issues with WebGL acceleration. Pica, for example, uses the Lanczos sampling algorithm at its core. I’ve implemented this algorithm with WebGL shaders, it’s not complicated, and early Pica used optional WebGL optimizations, but now it’s WASM. The reason for this decision is that WASM can guarantee the same results as JS for the same parameters, but WebGL cannot. For a discussion see here: Issue #114 · Nodeca/PICa

So WASM isn’t the only front-end salvation for computation-intensive tasks, but rather gives you a trade-off between performance, development costs, and performance. In my personal impression, the front end in graphics rendering outside the need to calculate the scene is not too much to tell the truth, like encryption, compression, mining, it is difficult to say that high frequency just need. As for AI applications that may be important in the future, IN the long term I prefer WebGPU as a next-generation standard that can better fulfill the potential of Gpus, and of course WASM is already a good option.

WebGL: It’s me, it’s me, I’m here first… Whether it’s 3D, image processing, or deep learning…

Can you improve performance by simply embedding WASM functions into JS?

Const add (a, b) => a + b => a + b => a + b => C

This is not necessarily true, because the JS engines in modern browsers come with one thing that is standard: the JIT. In short, if the add function is always adding integers, the JS engine will automatically compile a copy of the machine code that calculates int A + int b instead of the original JS function. This will greatly improve the performance of the function. This is why JIT calls just-in-time compilation.

So, don’t think of manual use of WASM to embed C when JS is slow. In fact, modern JS engines can help you “automatically convert JS to C”! If you can rewrite a JS function to the equivalent C, then I suspect that if the function is isolated, the JIT of the JS engine is likely to achieve similar performance. This is why V8 developers have the courage to pair JS with Rust.

In this article, Lin Clark does a great job of discussing the whole optimization process, resulting in faster function calls between JS and WASM than between non-inline JS functions. However, there is no mention of how this compares to JS function calls that are jIT-inlined.

Here’s a side note: Mozilla often advertises its super-large optimizations, and many of them may be due to obvious design problems (which, to be fair, we ourselves do). What are the root causes of big power-saving optimizations like Firefox 70 on the Mac last year? A rough understanding is that old Firefox on the Mac actually updated the window pixel in full every frame! Of course, these articles are quite informative, and it is highly recommended that you read the original text after you have laid the foundation, at least for a larger world, and often for software architecture design.

If SUBSEQUENT WASM supports GC, embedding intermodulation is likely to be more complicated. For example, I recently tried to manually synchronize large objects between the Flutter Dart and Android Java in the hope of “embedding some android platform capabilities into the Flutter system”. However, this resulted in a lot of long and low performance glue code that required deep copying via asynchronous messages with very little control. While WASM does not currently have GC, once it does, I have reason to suspect that it will have similar problems with object lifecycle management with JS. It’s just that this problem is mostly for Mozilla and Google to worry about, not us.

It doesn’t matter what parameters you pass. Because there are no more functions, it’s worth calling. Pointers that cannot be conveyed are no longer needed. Because there’s no one left to love.

Calling WASM in JS is as easy as calling C in Python?

This is a question that can only be addressed if it is actually done. Here are some of the things I’ve tried recently:

  • Call C++ in android’s Java class
  • Call C in the Dart of Flutter
  • C is called in QuickJS, an embedded JS engine

They all do one thing: create a native object in the engine, pass it to C/C++ function calls directly by reference, and use the engine’s GC to manage the object’s life cycle. This approach, commonly known as FFI (Foreign Function Interface), allows native code to be embedded in the language Runtime. But with two different runtimes, things are not so simple. For example, the QuickJS to Java binding project Quack requires Marshalling (serialization and deserialization similar to JSON) on both the JS and Java objects.

What about WASM? Basically, WASM’s linear memory space can be read and written with JS without the trouble of deep copy. However, WASM has only stream data types of int and float, not even string, so it is difficult to write out the structure of both JS and WASM by hand for slightly more complex objects. Now wheels like WASM-Bindgen do the dirty work. However, this process does not embed C/C++ functions directly into the JS Runtime, which is quite different from traditional FFI compiled to machine code.

As for JS objects that cannot be expressed in WASM’s Memory objects, there is a double happiness problem. For example, if you need to manipulate JS objects frequently with WASM these days, it will almost certainly affect performance. The typical pit for this is OpenGL applications based on WASM porting. A glTexImage2D function in C++, for example, is currently compiled into WASM by going from WASM to the JS glue layer, and then calling the WebGL API like gl.teximage2d in JS. Finally, the native graphics API can be called via a C++ binding. So from one layer of glue to two layers, the performance is not to say compared to native C++, can write JS directly?

Mozilla, of course, is aware of this problem, so they are trying to figure out how best to open Web IDL (the binding of the browser’s native API) to WASM, and in the process came up with the concept of WASM Interface Types: Since WASM is an intermediate layer of bytecode, why not give it an IR specification that can be used for all programming language runtime types? However, the specification expects to solve the problem primarily with protocolized, structured deep copies, and only future AnyRef types will be passable. Anyref is a bit like the file descriptor in Unix, so I won’t expand it here.

So maybe WASM will have DOM access in the future, but for now it’s just pie in the sky. Not to mention the fact that some extensions to WebGL are explicitly not supported by Web IDL, 2020 still seems a long way off, even if all the major Web standards are open to WASM and accessible to mainstream users.

Browser: Why did this happen… For the first time, you have a high-performance scripting language that is compatible with a high-level native language. The two pleasures overlap. And these two joys, brought more happiness. Should have been like a dream of happiness… But why is it like this…

Will the front-end framework be rewritten with WASM sooner or later?

I find it difficult or the ROI is not enough. Because mainstream front-end applications are IO intensive rather than computational intensive, WASM’s increased computing power is unlikely to become a bottleneck and will increase the maintenance cost of many projects.

One argument for this is the introduction of Google’s Jit-Less V8. V8’s peak performance dropped to less than a tenth of its original level with JIT off (see QuickJS Benchmark), but it barely affected the performance of a mild application like YouTube. Under Speedometer, which simulates heavy Web application loads, The run score was also around 60%, with only an order of magnitude difference in Webpack-type tasks. Do you think moving to WASM and doubling peak computing power will be disruptive in event-driven, IO intensive GUI scenarios? Can framework authors be persuaded to completely abandon their existing JS code base and rewrite the framework in another language? In the long run, WASM relies on a lot of JS glue code and polyfills that are large enough to affect the performance of the first screen.

Rewriting the mainstream UI framework with WASM means that the front end relies heavily on a completely different language technology stack. Are you saying that because the JVM is faster than V8, Node applications should be rewritten in Java? I see the political correctness in the front circle is the other way around…

I even is dead, nail in the coffin, also want in the tomb, with this decayed vocal cords shout out: JS cow force!! (Gag order)

WASM is a front-end ecosystem?

I don’t think so. Remember, a WASM application, with its compilation toolchain and dependency library ecosystem, basically doesn’t involve JS at all.

In 2018, I tried to compile FFMPEG to WASM. The whole process had almost nothing to do with JS. It was all about building a Docker build environment and modifying makefiles. At my level, the whole process was very confusing to me.

Later, while working with embedded Linux and Android, I stumbled on the concept of toolchains. A native application requires compilation, assembly, and linking to become an executable. For example, if my developer is a Mac, a typical set of toolchains for macOS would look something like this:

  • Macos-oriented toolchain is a set of clang compiled into macOS binary format
  • Aarch64-linux-android-gcc: aARCH64-linux-Android-gcc: aARCH64-linux-Android-gcc: AARCH64-linux-android-gcc: AARCH64-linux-android-gcc
  • – PSP toolchain, MIPS – GCC compiled to MIPS binary format
  • The WASM toolchain is the Emscripten set for WASM bytecode format

The last three are compiled to other platforms, so they are called cross-compiled.

The glTexImage2D API that you call after including

is provided by the dynamic library. Dynamic libraries enable the API to run consistently on x86 / ARM/MIPS/WASM platforms (like the.so format on Android). Emscripten, for example, provides a set of dynamic libraries compiled into JS format for the WASM platform. But it can only guarantee that these apis will work; performance is another story. It also makes a number of recommendations for optimizing the sexual bottlenecks in migrating WebGL.

So again, the dependency libraries and entire toolchains required to compile WASM applications have almost nothing to do with JS. JS, like machine code, is simply an output format compiled by someone else’s tool chain. From the perspective of JS developers, the whole thing may seem rather obtrusive. But from the perspective of a native app developer, it’s all perfectly normal.

Courtesy on one side only lasts not long. WASM can bring in other languages, but JS can’t go out? In addition to the revisionist route of Flutter, Static TypeScript and QuickJS, which I have described in detail, are the reverse outputs of JS fans:

  • As TypeScript continues to gain popularity, will there be runtimes that run directly to TypeScript?
  • Render React to the embedded LCD

Having said that, what are the scenarios for WASM? The initiatives being promoted by the WASM community, such as WASI, multithreading, and GC, have little to do with the JS ecosystem, but rather with the need to move more complex native applications directly to the Web. Now that we’re talking about this, don’t you see it? This is a typical xuesai-style road built on the plank road. (Laughter)

To summarize, JS and WASM’s hypothesis looks something like this:

  • JS: I was here first. Wherever there is a browser, I play. Some people don’t like my temper, but I go everywhere with a long, black string script. There are engines chasing me, but I’m always a browser loyalist first and foremost.
  • WASM: I’m Kaolin Flower. I’m welcome inside and outside the browser, and anyone can compile to me, so please use my binary format. Although I really want to JS and browser three people together forever, but most hope that cross-platform can be happy forever as long as I work hard.

WASM: ah… It would be nice if JS were a strong type of boy.

Which do you think is more to your taste, Ming Ming’s winter horse (JS) or Kaolin Flower’s snow cabbage (WASM)? Just so you know, for some white guy who’s been screaming for everything I want, double happiness is not something you can afford.

Generally speaking, in the whole white scholar (programmer) group, the base of the winter horse party is the largest, but the hardcore players are often the Snow vegetable party. As for me… What’s the name of the flower in my profile?

Last photo, winter horse and snow vegetables are good!

Afterword.

WASM is certainly a revolutionary technology that represents a new direction across platforms and has great business value for native app developers in particular. But it’s really just a browser-built bytecode virtual machine for the front end, not a panacea for all performance problems. At present the net many praise to it, in my opinion how much some overpraise. Therefore, it is suggested that we should not blindly follow the trend, or start from the white, ah not the basis of computer science, to judge the applicable scene and value of a technology.

The illustrations in this paper are from the high-performance front-end application draft PS implemented by pure JS. The white part is purely fun, please do not read too much (for example, why JS Logo is snow vegetable yellow, while WASM Logo is winter mauve, etc.). My level is limited, welcome you to further exchange technical views of this article, but also hope that you can pay attention to my “front-end thoughts” this free technical column ~