DEV Community

jtenner
jtenner

Posted on • Updated on

Web Assembly isn't always the Answer

When I hear the term "JavaScript Killer," I associate that with a desire to "kill JavaScript." In fact, all of us want to circumvent the problems that come with adding interactivity on our website or our web servers. The reason why is because JavaScript speed and performance is a game of "What Sacrifice do I have to make to encourage this software to function well?" Nobody likes this.

It's just that most comments about this problem will stem from a pool of negativity, which I believe is completely and utterly misplaced. I wish to address some of the speed claims of Web Assembly by pointing out that it isn't always the solution in the hopes that resentment of JavaScript dissipates.

Let's start with some basics of Web Development, which involve execution speed, memory usage, garbage collection times, frame rates, and ease of development. Having Web Assembly as a tool can (and does) address many of these concerns which are speed and memory usage.

Every aforementioned concern can be handled in JavaScript meaningfully, with the exception of ease of development, because that requires personal responsibility and a smart attitude. However, for speed and garbage collection, I now pick AssemblyScript as my tool of choice.

AssemblyScript itself addresses a lot of the problems I wanted to solve. It has managed memory, garbage collection, and it was "easy enough" as a trade-off because I was able to write my software in a special flavor of JavaScript. It happens to be my personal focus, and I loved it so much that I developed an entire testing framework for it.

There is a lot to be said about AssemblyScript and I wrote a primer about it here. I want to use it as an example of why Web Assembly isn't exactly always the answer to solve every one your problems. You can check out the GitHub here (and give it a star):

GitHub logo AssemblyScript / assemblyscript

Definitely not a TypeScript to WebAssembly compiler 🚀

AssemblyScript logo

Test status Publish status npm compiler version npm loader version Discord online

AssemblyScript compiles a strict variant of TypeScript (basically JavaScript with types) to WebAssembly using Binaryen. It generates lean and mean WebAssembly modules while being just an npm install away.

About  ·  Introduction  ·  Quick start  ·  Development instructions

Contributors

Contributor logos

Thanks to our sponsors!

Most of the core team members and most contributors do this open source work in their free time. If you use AssemblyScript for a serious task or plan to do so, and you'd like us to invest more time on it, please donate to our OpenCollective. By sponsoring this project, your logo will show up below. Thank you so much for your support!

Sponsor logos






For our purposes today, we will talk about some of the problems that I faced when developing the as-pect testing command line tool. More specifically I hit a snag when trying to make as-pect faster by pushing more logic into Web Assembly.

Everyone knows that Web Assembly is fast and efficient bytecode. Thus, it should be the case that porting your application into Web Assembly should be the optimal solution. This is the very assumption I started with.

You see, as-pect has all of the test running logic in JavaScript. More specifically, it's compiled from TypeScript, and run using the node.js runtime via command line. When it came time to start the optimizing process, I noticed that it was possible to move all of the testing logic into the web assembly testing module itself.

A first gut reaction might be to think that because the test logic now runs in Web Assembly, that things would become way faster.

This was not the case.

Why not port the testing framework into AssemblyScript? Well, imagine needing to compile over 25+ entry files that contain some describe() function calls. Each of them must be interpreted separately for reporting and organizational reasons. Even worse, WebAssembly currently doesn’t support dynamic linking of functions. Thus each test’s binary must be statically linked. What does that mean? A copy of the entire testing library must be put into each testing module.

This might not seem like a big deal, but as-pect itself runs over 200 tests over the course of 25+ .spec.ts files. This means 25 copies of the testing framework need to be made. This framework would need to be compiled, interpreted, and bootstrapped nearly 25 more times overall.

Now think about it from the JavaScript perspective. The bootstrapping framework only needs to be parsed, loaded, executed, and optimized once before the framework picks up speed and V8 does it's job. In conclusion, V8 was a fantastic dynamic solution in this case. Moving the testing logic to Web Assembly prevented all of the battle tested performance gains of JavaScript, in exchange for a much larger startup time.

This was not the only problem. It was compounded by the fact that not every single testing framework function can be described in web assembly. For instance, getting the current time must currently be obtained using the performance.now() function. The wasi alternative is not supported in node.js yet.

The number of imported functions might have decreased, but calls into JavaScript also increased. Sure, these things could be optimized, but there were many other tiny little things that added to the pile of "fake preoptimization" code smell. The end result was measurable, decreasing overall execution speed by about 50%. This was simply because I was asking a compiler to leverage more work that was actually the responsibility of the JavaScript host to perform.

The overall tradeoff was a miserable 10% speed increase in testing speed, which was only 25ms per file already. This didn't make things any faster. It made the testing framework as a whole way worse. This is because compilation times were the bottleneck, not the testing. Also, this direction causes problems with feature flexibility.

Spending your time thinking that you can port every problem you have to Web Assembly is not productive. You may be creating problems you previously did not have to deal with. I know that AssemblyScript can make your TypeScript faster, but it's not always the solution. Don't let this fact prevent you from trying to make your software better. Instead, let it guide you down a path that utilizes the right tool for the job.

For example, letting V8 optimize your JavaScript is sometimes the best solution you can choose because JavaScript is very flexible. Everyone who writes JavaScript knows this. Writing code that can be executed in node.js immediately helps you circumvent the aforementioned slow compile times and cumbersome build steps unless you are coding in TypeScript. Qualitative top-level tasks like adding and testing a new command line interface option will happen much quicker. There are many tools already unit tested and optimized just for this kind of purpose, and thus, we should be responsible for standing on the shoulders of giants to achieve our higher goals.

It means you should start with JavaScript, and make little parts of your code faster using a compiler. If you can test the speed and functionality of your software, and prove that Web Assembly is faster, use it. Period. Don't look back. However, even the wisest of house builders are told to measure their wood before they cut it. You are responsible for building a house that people will live in, and thus, should make sure the job gets done correctly.

Attack your problems like the wise builder and not as the zealous idea seeker.

Happy Coding.
-jtenner

Top comments (5)

Collapse
 
cubiclebuddha profile image
Cubicle Buddha

Would you be willing to clarify what AssemblyScript means by “a strict subset of TypeScript”? Like what is not allowed, and how could it be enforced? Like is there a linter? Or does the custom compiler do that enforcement for you?

Collapse
 
jtenner profile image
jtenner • Edited

The answer is a difficult one to describe in a few comments. If you don't mind waiting, I have a few blog posts I'm writing that are incomplete.

I can answer your question exactly in the near future and merely ask that you keep reading!

Thanks for this excellent question.

Collapse
 
cubiclebuddha profile image
Cubicle Buddha

Alright, you’ve got a new follower. And thank you for following me! I have some TypeScript-related articles coming up soon too.

Collapse
 
trusktr profile image
Joe Pea

Nice article. I suppose that explains what I experienced while on the rtrace branch. :)

BTW, you mentioned

Writing code that can be executed in node.js immediately helps you circumvent the aforementioned slow compile times and cumbersome build steps unless you are coding in TypeScript

Just wanted to mention I've been using ts-node in transpileOnly mode, in a relative big application, and the speed difference between plain JS and ts-node is almost imperceivable (and also cached on re-runs).

Collapse
 
jtenner profile image
jtenner

AssemblyScript also requires ts-node version 8 which is not the latest version. This is because of some kind of tsconfig problem with AssemblyScript. If I had to use it, I would not get the latest version. I'll try to keep this in mind for later because it's something I could measure against.