In this article, I will explain how easy it is to implement WebAssembly into your web applications, and go through simple performance testings regarding JavaScript and WebAssembly.
There are plethora of programming languages nowadays, and different languages have different pros and cons. As for languages I have worked with, I would rate C/C++ > Java > JavaScript in terms of performance, and JavaScript > Java > C/C++ in terms of productivity, while it would depend on the programmer. In the world of JavaScript, the emergence of Just-in-Time (JIT) Compiler instigated a rapid development, and has become a foundation upon which programmers used to build larger and larger web applications. However, while everyone was still unsatisfied with the performance of JavaScript, came the WebAssembly.
Two aspects that makes the biggest difference in performance arise when the compiling happens (compile language vs. interpreter language,) and the lack of a need for a virtual machine (VM), as WebAssembly code can be executed directly in Assembly. As most of you know, JavaScript, due to the flexibility and productivity, has its patented pros and cons. It is interpreted, compiled, and executed in runtime, so in terms of performance, such sacrifices are quite clear.
There are toolchains out there that allow you to transform any asm.js or C/C++ code written for and working in FireFox into WebAssembly modules like ones shown below.
However, to a Front-end developer, it is a long way ahead to get to fast, performant WebAssembly modules. In order to bestow glorious WebAssembly onto your program, you need to go through and understand the confusing concepts including the make, llvm, C/C++, compile, and linking. Then, you can use Webpack or rollup to integrate the created modules in a development environment. It is no wonder that most of the introductory material on WebAssembly only include creating the main function in C/C++ to print out a “Hello, World.”
It is very likely that you would be exhausted before you can start applying the knowledge to useful applications.
There are some prerequisites to languages that can be converted into WebAssembly, and it is that the variable types can be categorized. Languages like C/C++, Rust, TypeScript(Issue on Compilation target other than JavaScript) can be converted into WebAssembly, and "AssemblyScript", a lower set of TypeScript, can also be converted into WebAssembly.
The reasoning behind choosing AssemblyScript is the utility. It is registered on NPM, and a front end developer can easily test the WebAssembly codes.
In this article, I will use AssemblyScript to convert my codes into a WebAssembly module.
Here are some warnings before we start writing WebAssembly codes using AssemblyScript.
If you install assemblyscript through NPM, you can easily compile and create a .wasm (WebAssembly) file over the command line. Furthermore, there are tools to use the created WASM files as modules like assemblyscript-loader and wasm-loader, and they mainly defer in that the first one has more options to load WASM modules from inside of the code, and the latter is provided using the Webpack loader, and can be bundled and used more easily.
As we mentioned before, using native languages like C/C++ to compile into WebAssembly is an extremely convoluted task. Let’s use JavaScript language family that is more familiar to write codes that can compile directly into WebAssembly and that can be bundled using the Webpack.
The two NPM packages offer convenience to some degree, but there are still shortcomings when it comes to writing the JavaScript code in the Webpack development environment to be bundled immediately. Therefore, I have written my own Webpack loader. The loader provides two main functionalities.
Note: There was an error when it comes to uglifying codes when using the wasm-loader, so I had to write the WASM loading portion myself.
Since I have not yet registered the package on NPM, install it through Github.
npm install --save-dev https://github.com/dongsik-yoo/assemblyscript-live-loader.git
Modify the config file to allow Webpack to bundle files written in AssemblyScript.
webpack.config.js
module: {
loaders: [
{
test: /\.asc$/, // assemblyscript Source File
exclude: "/node_modules/",
loader: "assemblyscript-live-loader"
}
];
}
Create a ./asc/Calculator.asc file, and fill in the following content.
export function add(a: int, b: int): int {
return a + b;
}
export function subtract(a: int, b: int): int {
return a - b;
}
export function multiply(a: int, b: int): int {
return a * b;
}
export function divide(a: int, b: int): int {
return a / b;
}
Now, the ostensibly complicated process of compiling, loading, and bundling the module can be simplified through the Webpack loader. Now, let’s import the WebAssembly module to use in our index.js file, and call the prepared test functions to fetch the results.
import Calculator from "./asc/Calculator.asc";
const calc = new Calculator().exports;
const add = calc.add(44, 8832);
const subtract = calc.subtract(100, 20);
const multiply = calc.multiply(13, 4);
const divide = calc.divide(20, 4);
console.log(add);
console.log(subtract);
console.log(multiply);
console.log(divide);
npx webpack
Note: It’s more convenient if you use NPM5.2’s newly added npx
Before I started writing this article, I was under the impression that the WebAssembly would be faster in general. Therefore, I wrote simple binary arithmetic operators and factorials using WebAssembly version that compiled JavaScript and AssemblyScript for a simple performance measurement. However, WebAssembly’s performance test results came out to be different from what I expected.
Note: Chrome and Firefox now use WebAssembly as defaults, and can be tested.
// Initialization Code
const factorialNumber = 1000;
const factorialLoop = 10000;
const N = 1000000;
...
// (JavaScript) Loops are executed in JavaScript
{
name: 'Factorial',
fn: function () {
var i = 0;
for (; i < factorialLoop; i += 1) {
CalculatorJS.factorial(factorialNumber);
}
}
},
// (JavaScript) Loops are executed in the test function.
{
name: 'Factorial',
fn: function () {
CalculatorJS.factorialWithLoopCount(factorialLoop, factorialNumber);
}
},
// (WebAssembly) Loops are executed in JavaScript
{
name: 'Factorial',
fn: function () {
var i = 0;
for (; i < factorialLoop; i += 1) {
CalculatorWASM.factorial(factorialNumber);
}
}
},
// (WebAssembly) Loops are executed in the test function.
{
name: 'Factorial',
fn: function () {
CalculatorWASM.factorialWithLoopCount(factorialLoop, factorialNumber);
}
}
// Javascript Test Code
function factorial(num) {
let tmp = num;
if (num < 0) {
return -1;
} else if (num === 0) {
return 1;
}
while (num > 2) {
tmp *= num;
num -= 1;
}
return tmp;
}
function factorialWithLoopCount(count, num) {
let i = 0;
for (; i < count; i += 1) {
factorial(num);
}
}
// WebAssembly Test Code
export function factorial(num: int): int {
var tmp: int = num;
if (num < 0) {
return -1;
} else if (num === 0) {
return 1;
}
while (num > 2) {
tmp *= num;
num -= 1;
}
return tmp;
}
export function factorialWithLoopCount(count: int, num: int) {
var i: int = 0;
for (; i < count; i += 1) {
factorial(num);
}
}
As many people have predicted, WebAssembly will not replace JavaScript, but instead will be used to enhance the performance of modules that can be executed faster than plain JavaScript.
There are unbelievably diverse factors that affect JavaScript’s performance. As I mentioned before, I for sure surmised that WebAssembly would be faster, but aside from the factorial operation, basic binary arithmetic operations were executed faster with JavaScript.
To list a few of numerous factors that contribute to JavaScript’s performance is as follows.
I have to assume that, in this test, as I ran repetitive loops, simple arithmetic operators became faster as they were compiled to Assembly in JIT. Also, it seems that the process of being converted from JavaScript to WebAssembly, also known as trampolining, has taken a serious toll on performance. Lastly, the difference between Chrome and Firefox cannot be ignored.
As the introduction of JIT initiated a fierce competition among JavaScript engines, the performance competition initiated by WebAssembly should also be a hoot and a half.
One of the issues I was worried about was how to debug the program when an error occurs in the WebAssembly module, but luckily, Chrome captures the Call-stack as below.