The State of Web Assembly in 2020
Tags: Web Development, JavaScript, Nodejs, Rust, Webassembly

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.