Applying Web Assembly to Web Applications Made Easy


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.

Ways to Convert Existing Modules into WebAssembly

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.

  • Code asm.js and Turn It Into WebAssembly (binaryen)
  • Code C/C++ and Turn It Into WebAssembly (Emscripten)

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.

AssemblyScript

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.

  • Clearly document the type in order to avoid implicit format changes.
  • It is necessary to initialize the default values of parameters.
  • Only support clear types (Does not support any or undefined types.)
  • Boolean Operators && and || always result in bool values.

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.

AssemblyScript Made Easier

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.

assemblyscript-live-loader

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.

  • Compiling AssemblyScript as WASM
  • Bunlding the WASM module so that it can be used as WebAssembly.Module

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.

Installing the Package

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

Webpack Loader config

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"
    }
  ];
}

Writing the Code To Be Compiled as WebAssembly in AssemblyScript

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;
}

Importing the Module

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);

Build

npx webpack

Note: It’s more convenient if you use NPM5.2’s newly added npx

Testing for Performance of JavaScript with WebAssembly

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.

Test Environment

  • Testing Tools: Chrome59.0.3071.115, Firefox 54.0.1, micro-benchmark
  • Test Results (Chart): TUI-Chart 2.9.0
  • Tested Features: Addition, Subtraction, Multiplication, Division, Factorial
  • Legends: JavaScript (Red), WebAssembly (Orange) Shorter bar graphs show faster performance.
  • Test Page: The aforementioned test is documented here.
  • Test Code: Test codes can be seen at respective links. Javascript, WebAssembly, Benchmark

Test Code Sample

// 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);
  }
}

Test Results from Chrome

chrome, performance loop in wasm chrome, performance loop in js

Test Results from Firefox

firefox, performance loop in wasm firefox, performance loop in js 1

Test Results and Closing Remarks

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.

  • Performance of the JavaScript Engine
  • Unexpectedly fast Performance Optimization of JIT
  • Call-stack Handling (ex> when using recursion)
  • Cost of calling WebAssembly functions
  • WebAssembly Compiler Optimization

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.

And The Rest

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.

error stack

Reference


DongSik Yoo, FE Development Lab2017.07.28Back to list