Web Assembly Vs. Javascript

By Anush Chandra Shekar Reddy

The first version of JavaScript (Mocha) was written in 10 days in May 1995. It wasn’t designed for performance, its sole goal was to be a scripting language to make the then static web dynamic. JS didn’t gain a performance boost until 2008 when browsers started using Just-In-Time (JIT) compilers to improve its performance by nearly 10 times. This performance gain has made JavaScript very popular and has led to unexpected uses such as server-side programming. With the introduction of WebAssembly and its gaining popularity, is Javascript a thing of the past? In this article, I’ll show you how to build simple WebAssembly modules and run benchmarks to compare it to JavaScript.

So, what is WebAssembly anyway?

WebAssembly (wasm) is a new low-level assembly-like language for the web which provides new features and major gains in performance. Wasm bytecode is encoded in a size and load time efficient binary format. Wasm has been designed to be a compilation target for low-level source languages such as C, C++, Rust,, etc. While it isn’t designed to be written by hand, the wasm compiler uses S-expressions to handle intermediate code (wast) which makes it possible for one to author, debug, test, analyze, and optimize wasm code in a textual representation. WebAssembly is also designed to be compatible with existing web technologies and also backward compatible.

WebAssembly key concepts

WebAssembly can be written in its intermediate textual representation (wast) and then be compiled to wasm using the wabt toolkit (https://github.com/WebAssembly/wabt). Alternatively,  C, C++ or Rust code can also be compiled to wasm using the emcc compiler with the linker flag WASM=1. MDN has a good guide for doing this at the foolowing link https://developer.mozilla.org/en-US/docs/WebAssembly/C_to_wasm.

Here, wasm code generated is assembly-level code which is compiled by the browser into executable machine level code. Wasm code is loaded, compiled and executed using the WebAssembly JavaScript API. Wasm Modules can be downloaded using ajax and loaded using the WebAssembly.instantiate(bytes,[imports]) API call which is passed the wasm code. This API call is a promise that returns the associated module and instance. In the future it will be possible to load wasm modules using the script tag like .

There are several key concepts associated with WebAssembly you must understand before you can actually write and use WebAssembly modules.

Module: A module represent wasm code that has been compiled into executable machine level

code. A module declared imports and exports. A module is stateless and hence can be

cached in IndexDB, shared between web workers and windows.

Memory: This is a resizeable array that contains the bytes read and written to by low-level

memory functions such as load and store.

Table: This is a resizeable typed array of references to functions, variables, etc.

Instance: An instance is a module with the state it uses during runtime.

Imports and Exports: Exports are wasm functions that are exposed for use via JavaScript and

imports are JavaScript functions that can be called by a wasm instance.

 

Performance of WebAssembly compared with JavaScript

To compare the performance of WebAssembly and JavaScript, I used the Benchmark.js library and executed the same function in both the languages. I looked at three cases: a simple function (addition of two numbers), a loop (fibonacci), and a recursive function (factorial). I used the following code as the skeleton for each test replacing func_to_compare() as needed:

 

Index.html

<!DOCTYPE html>
<html>
    <head>
        <title>WebAssembly Vs. JavaScript</title>
    </head>
    <body>
        <h3>Results</h3>
        
id="results">
</body> src="lodash.js"> src="platform.js"> src="benchmark.js"> type="text/javascript" src="index.js"> </html>

Index.js

function fetchAndInstantiate(url) {
  return fetch(url).then(response =>
    response.arrayBuffer()
  ).then(bytes =>
    WebAssembly.instantiate(bytes)
  ).then(results =>
    results.instance
  );
}

function compare(wasm_lib) {
	var suite = new Benchmark.Suite;

	suite.
	  add('wasm', function() {
	    wasm_lib.func_to_compare();
	  }).
	  add('js', function() {
	    func_to_compare();
	  }).
	  on('cycle', function(event) {
	  	var textNode = document.createTextNode(String(event.target)); 
	    	document.getElementById('results').appendChild(textNode);
	  }).
	  on('complete', function() {
	  	var text = 'Fastest is ' + this.filter('fastest').map('name');
	  	var textNode = document.createTextNode(text); 
	   	document.getElementById('results').appendChild(textNode);
	  }).
	  run();

}

function func_to_compare() {
	...
};

fetchAndInstantiate('module.wasm').then(function(instance) {
	compare(instance.exports);
});

The first function is the addition of two numbers.

Addition of two numbers – wasm Addition of two numbers – JS

(module
  (func $add (param i32 i32) (result i32)
    (i32.add
      (get_local 0)
      (get_local 1)))
  (export "add" (func $add))
)

function add(a, b) {
  return a + b;
}
Results for add(12345, 56789):

wasm x 11,411,846 ops/sec ±5.99% (56 runs sampled)

js x 1,559,690,563 ops/sec ±5.30% (55 runs sampled)

Fastest is js

Results for add(123456789, 987654321):

wasm x 12,033,572 ops/sec ±4.44% (58 runs sampled)

js x 1,539,061,410 ops/sec ±7.48% (55 runs sampled)

Fastest is js

Nth Fibonacci number – wasm Nth Fibonacci number – JS
(module

(module 
  (func $fibo (param $i i32) (result i32)
    (local $a i32)
   (local $b i32)
   (local $tmp i32)
   (set_local $a (i32.const 1))
   (set_local $b (i32.const 0))
   (block $done 
     (loop $loop
      (if (i32.le_s (get_local $i) (i32.const 0)) (br $done)
        (block
         (set_local $tmp (get_local $a))
         (set_local $a (i32.add (get_local $a) (get_local $b)))
         (set_local $b (get_local $tmp))
         (set_local $i (i32.sub (get_local $i) (i32.const 1)))
       )
     )
     (br $loop)
   ) 
   )
  (get_local $a)
  )
  (export "fibo" (func $fibo))
)

function fibo(num){
  var a = 1, b = 0, temp;

  while (num >= 0){
    temp = a;
    a = a + b;
    b = temp;
    num--;
  }
  return b;
}
Results for fibo(12):

wasm x 11,284,780 ops/sec ±2.79% (59 runs sampled)

js x 61,445,847 ops/sec ±6.36% (49 runs sampled)

Fastest is js

Results for fibo(140):

wasm x 4,229,981 ops/sec ±8.42% (56 runs sampled)

js x 2,298,847 ops/sec ±4.62% (55 runs sampled)

Fastest is wasm

Factorial of N – wasm Factorial of N – JS

(module
  (func $fact (param $n i32) (result i32)
    (if (i32.lt_s (get_local $n) (i32.const 1))
      (return (i32.const 1)))
        (return
        (i32.mul
          (get_local $n)
          (call $fact
            (i32.sub
              (get_local $n)
              (i32.const 1))))))
  (export "fact" (func $fact))
)

function fact(n) {
  if (n <= 0) {
    return 1;
  }
  return n * fact(n - 1);
}
Results for fact(5):

wasm x 11,322,759 ops/sec ±2.77% (56 runs sampled)

js x 85,854,402 ops/sec ±4.19% (57 runs sampled)

Fastest is js

Results for fact(100):

wasm x 3,505,575 ops/sec ±1.75% (63 runs sampled)

js x 2,165,469 ops/sec ±3.47% (58 runs sampled)

Fastest is wasm

Conclusion:

            In the above results, WebAssembly doesn’t seem to have performance benefit over JavaScript for simple computations such as addition of two numbers, generating factorial of a smaller number, etc. However, for more complex computations, WebAssembly has a really good performance gain (60% for fact(100) and 84% for (fibo(140)). WebAssembly has been designed to be used in conjunction with javascript. Simple operations should be performed with JavaScript and more complex operation offloaded to WebAssembly. Performing complex operations with WebAssembly not only improves performance, it eliminates the need to download and parse large JavaScript files. WebAssembly would be an excellent choice for games, image/video editing, encryption, server-side applications, and performance intensive tasks.

WebAssembly, right now is still in its development phase and only the MVP has been released so far. There are many features missing such as support for complex types like arrays, garbage collection, etc. While it’s not a good idea to write an entire application in WebAssembly, it is a great time to play around with it as it is gaining popularity.

PS: The code from this article can be found at https://github.com/anush-cr/wasm-performance

 

 

References

  1. http://webassembly.org/getting-started/developers-guide/
  2. https://github.com/WebAssembly/design/blob/master/UseCases.md
  3. https://developer.mozilla.org/en-US/docs/WebAssembly
  4. https://github.com/WebAssembly/wabt
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s