The State of Web Assembly in 2020

aesher9o1
JavaScript in Plain English
6 min readJul 19, 2020

--

Architecture of V8

A benchmarking overview to help you decide when NOT to use Web Assembly.

There’s a new hype in JS world, Web Assembly. And those familiar with Turbofan’s compilation pipeline will we very right to argue that the code produced by liftoff skips a lot of steps that a normal javascript. Making your code “hot” faster. As per the rule of thumb on the official Web Assembly page: “ Use WebAssembly for computationally intensive tasks, such as games, image manipulation, math, physics, audio effects, etc….”. The question that strikes first is, why not write every little function in web assembly? Every increment to an integer, every addition. What could the tradeoff be? Well, let’s find out.

Configuration used for benchmark

Processor: Intel(R) Core(TM) i5–8265U CPU @ 1.60GHz
RAM: 16GB
OS: Ubuntu 20.04 LTS

Disclaimer. The benchmarking results may vary from PC to PC. The following data should not be taken as the final execution time for WASM and JS files but rather provides a relative comparision to draw a line between JS ans WASM for your calculations. Also the following data compares JS with Rust, speed with C++ or other languages may vary.

Aim: Measure the time taken for O(N) and O(N²) loops under the following conditions:

a. RAW loop time
b. Sum of elements of the array passed as a value
c. Allocation of memory for an array

Benchmark for O(N) loops:

RAW loop time:

//Rust example that iterates over 0 to length(variable)#[wasm_bindgen]pub fn loop_time_1d_arr() {let mut sum = 0;for i in 0..440000 {sum += i;}}
//Javascript loop that iterates from 0 to length1d(variable)function loopTime1DArr() {let sum = 0;for (let i = 0; i < length1d; i++) {sum += i;}}
Time in milliseconds

WASM somehow maintained a constant execution time and resulted in 0 milliseconds execution time. The execution time for javascript however seems to increase linearly. The bump between 200,000 and 300,000 however could be because of the CPU overheating or any background task that might have been running in the background.

Sum of elements of the array passed as a value:

//Rust program to pass array as a value to a function to calculate the sum#[wasm_bindgen]pub fn sum_of_arr(arr: &JsValue) -> i32 {let mut sum = 0;let elements: Vec<i32> = arr.into_serde().unwrap();for i in 0..elements.len() {sum += elements[i];}return sum;}//Javascript program to pass array as a value to a function to calculate the sum
function sumOfArr(arr) {
let result = 0;for (let i = 0; i < arr.length; i++) result += arr[i];return result;}
Time in milliseconds

Hmm… WASM breezed in the previous benchmark, but what happened now? Turns out into_serde() method that is used here adds a huge load of parsing and optimizing the dynamically typed code from JavaScript thus giving us this graph.

Allocation of memory for an array:

//code to allocate array of dynamic length in Rust#[wasm_bindgen]pub fn malloc_1_d_arr() {let mut vec: Vec<i32> = [].to_vec();for i in 0..440000 {vec.push(i);}}//code to allocate array of dynamic length in Javascript
function malloc1darr() {
let result = [];for (let i = 0; i < length1d; i++) result.push(i);}
Time in milliseconds

It can be seen here that WASM is back in business giving 4X-6X better results as compared to JavaScript.

Benchmark for O(N²) loops:

RAW loop time:

// Code to perform O(N^2) loop in rust
#[wasm_bindgen]
pub fn loop_time_2d_arr() {let mut sum = 0;let length = 6000;for _ in 0..length {for j in 0..length {sum += j;}}}// Code to perform O(N^2) loop in Javascript
function loopTime2DArr() {
let sum = 0;for (let i = 0; i < length2d; i++)for (let j = 0; j < length2d; j++) sum += j;}
Time in milliseconds

The size chosen was arbitrary taken so as to generate the fastest output to fit my schedule. Same as the previous results we can see the execution time of javascript on the higher side.

Sum of elements of the array passed as a value:

//Sum of 2D Array passed as value Rust
#[wasm_bindgen]
pub fn sum_of_n2_arr(arr: &js_sys::Array) -> i64 {let mut sum = 0;let elements: Vec<Vec<i32>> = arr.into_serde().unwrap();for i in 0..elements.len() {for j in 0..elements[i].len() {sum += elements[i][j];}}return sum.into();}// Sum of 2D array passed as value Javascript
function sumOfN2Array(arr) {
let result = 0;for (let i = 0; i < arr.length; i++) {for (let j = 0; j < arr[i].length; j++) {result += arr[i][j];}}return result;}

As confirmed in the previous benchmark it can be seen that the parsing time at the Rust side increases with the increase of payload.

Allocation of memory for an array:

//Allocation of 2D Array Rust#[wasm_bindgen]pub fn malloc_2_d_arr() {let mut vec: Vec<Vec<i64>> = [].to_vec();let length = 6000;for i in 0..length {vec.push([].to_vec());for j in 0..length {vec[i].push(j as i64);}}}// Allocation of 2D array JavaScript
function malloc2darr() {
const demoArrayOfNumber = new Array(new Array());for (let i = 0; i < length2d; i++) {demoArrayOfNumber[i] = [];for (let j = 0; j < length2d; j++) demoArrayOfNumber[i].push(j);}}
Time in milliseconds

Unlike previous benchmarks, JavaScript seems to keep up with Rust and give outputs at par to Rust.

Conclusion:

Rust sure proves to be performant and better than Javascript in comparison to speed. However, as the benchmark shows, a huge computation is required to transfer a statically typed data from JS to Rust. Hence the performance gain can only be made visible if the data that has entered rust goes through a lot of iteration and makes the processing worthwhile. For example the present use cases of WASM as barcode scanners, games etc require a lot of computation rather than just looping through an array. If you find yourself stuck in such a computation loop where you see drop frames and laggy browser, WASM would surely prove to be a savior. Otherwise, the 2X-3X gains you get by WASM as part of its computation will easily be overshadowed by the enormous data transfer rates by WASM.

If you want to replicate the following test on your PC or see the raw data. Find all the resources below.

--

--

Sometimes it is the people no one can imagine anything of, do the things no one can imagine.