Go WebAssembly Performance Benchmark

Estimated reading time: 7 Minutes

In my previous post I explained what WebAssembly is and how to run a simple Go web application with WebAssembly in the browser. A question I had in mind during the writing of that post is if Go WebAssembly really is faster than JavaScript? In this post I’ll conduct a research and a Go WebAssembly performance benchmark to try and answer this question.

Existing Research

Prior to conducting the actual benchmark, I wanted to see what other benchmarks exist in this field. In order to get a sense of what’s the best approach to conduct such a benchmark I also tried to see what others lacked.

I would have to admit that the more I read, the more I got lost. Every research/blog post I read had a contradicting one explaining why the former wasn’t benchmarking the right part, or how it could have tested things better.

Micro-benchmarking

One of the quotes that stuck with me was from Colin Eberhardt as part of his talk – WebAssembly and the Death of JavaScript:

“One of the first questions people ask when they look at WebAssembly is this is going to be super duper performance. It’s going to be really really fast. I must admit I read a lot of blog posts which makes some quite impressive, and to be honest, quite incorrect claims about the performance of WebAssembly. I’ve seen blog posts claiming that it’s a hundred times faster than JavaScript. Unfortunately, most of those blog posts suffer from the classic problem of doing micro benchmarking or either benchmarking the wrong thing.”

Colin Eberhardt @ WebAssembly and the Death of JavaScript

I wanted to be sure I’m not going to benchmark the wrong thing. I do believe it’s fine to micro benchmark, as long as you are aware of doing so.

In order to understand at what aspect it is expected for WebAssembly to be faster than JavaScript I had to dig deeper. Naturally, the place I chose to start is the paper first published by the WebAssembly team.

WebAssembly Launch Paper

This paper, titled Bringing the Web up to Speed with WebAssembly, was published in 2017. It was composed by an impressive representative list from Google, Apple, Microsoft and Mozilla. The paper describes the reasons for which WebAssembly was formed and how it’s design came together.

In regards to performance, they conducted a benchmark named PolyBenchC on both Chrome and Firefox. PolyBench is a benchmark suite of 30 numerical computations with static control flow, extracted from operations in various application domains (linear algebra computations, image processing, physics simulation, dynamic programming, statistics, etc.).

PolyBenchC performance results

4 out of the 24 benchmarks ran quicker than native code and 7 of the benchmarks ran within 110% of native code execution time. Only 1 benchmark took more than twice the time of native code execution.

To keep things short, the paper concluded that WebAssembly should be about 20% to 30% faster at runtime execution than JavaScript. But, because JavaScript is really quite fast, there isn’t that much of a performance gain to be made. Where they expect WebAssembly to beat JavaScript significantly is parse time and the initial compilation time.

More Benchmarks

Another notable benchmark is the one conducted by Franziska Hinkelmann as part of her talk – Speed, Speed, Speed: JavaScript vs C++ vs WebAssembly. In this talk she compares JavaScript to C++ and to WebAssembly. Her conclusion from this benchmark is that although C++ and WebAssembly are faster than JavaScript, the difference is in a factor of 2 which is not significant. Modern JavaScript, thanks to many years of use and optimizations, is really fast.

JavaScript vs C++ vs WebAssembly

A few last sources I would like to mention:

  • Performance Testing Web Assembly vs JavaScript: The benchmark conducted in this blog post, involved several algorithms of matrix multiplication with C++ compiled to WebAssembly vs JavaScript. The results showed that JavaScript performed better than WebAssembly on smaller array sizes and WebAssembly performed better than JavaScript on larger array sizes.

My Micro-benchmark

Since we concluded that WebAssembly outshines JavaScript in parse time and the initial compilation time I tried to find a way to test these differences. Since I wasn’t able to do so I decided to create a benchmark similar to the last ones I mentioned.

I’m deliberately calling it a micro-benchmark, since I am now aware of the fact that I’ll be testing a specific difference between the two. The benchmark will focus on execution time. I will also try to highlight at what situations WebAssembly would perform better than JavaScript and vice versa.

First let’s create two similar implementations of Fibonacci series calculation.

Iterative Implementation

Go:

func fibonacci(num int) {
	a := 1
	b := 0
	var temp int
	for num >= 0 {
		temp = a
		a = a + b
		b = temp
		num--
	}
}

JavaScript:

function fibonacci(num) {
    let a = 1, b = 0, temp;
    while (num >= 0){
        temp = a;
        a = a + b;
        b = temp;
        num--;
    }
}

Let’s run both functions from the browser, for this purpose I’ll be working with Chrome Version 84.0.4147.135 (Official Build) (64-bit).

Go-WebAssemblyJavaScript
fibonacci(5000000)7.735 [ms]25.238 [ms]

Pretty cool right? one would argue from these results that Go WebAssembly is about 3 times faster than JavaScript.

But that’s not really the case. Let’s try and run this test a few times more in the same session and see how the results change:

IterationGo-WebAssemblyJavaScript
17.735 [ms]25.238 [ms]
28.724 [ms]23.857 [ms]
39.664 [ms]6.828 [ms]
47.660 [ms]5.875 [ms]
57.870 [ms]7.147 [ms]
67.515 [ms]6.458 [ms]
WebAssembly vs JavaScript

Whoa! What have we here? well the answer is optimizations. As mentioned above, JavaScript has been around for a while, and browsers have managed to optimize it under the hood with the V8 engine in several ways such as: JIT compilation, speculative optimizations and so on. We can actually see that JavaScript and WebAssembly in this case perform quite the same.

Iterative V2

So if we take into account optimizations, let’s see how will these come in place if we invoke the same function with different inputs:

Go:

func fibonacciV2(iterations int) {
	for i := 0; i <= iterations; i++ {
		fibonacci(i)
	}
}

JavaScript:

function fibonacciV2(iterations) {
    for (let i = 0; i <= iterations; i++) {
        fibonacci(i);
    }
}

And let’s see the results:

Go-WebAssemblyJavaScript
fibonacciV2(60000)2.855 [s]2.547 [s]

Not surprisingly we get similar results to the ones where we ran the same function several times.

Switching Functions

After noticing that the Fibonacci series calculation yielded similar results for both WebAssembly and JavaScript, I decided to run my benchmark again with a less common but more computationally intensive function:

Go:

func slow(num int) {
	var res float64
	for i := int(math.Pow(float64(num), 10)); i >= 0; i-- {
		res += math.Tan(float64(i)) * math.Atan(float64(i))
	}
}

JavaScript:

function slow(num) {
    let res = 0;
    for (var i = Math.pow(num, 10); i >= 0; i--) {
        res += Math.tan(i) * Math.atan(i);
    }
}

This function uses complex tan and atan functions which are harder for the V8 engine to optimize. I guess that this is the sort of computation that one would expect from a rather complex application.

Go-WebAssemblyJavaScript
slow(6)7.027 [s]9.369 [s]

These results also remain the same with more iterations, which is not what we got for the Fibonacci function!

Slow Function V2

As a final step I wanted to measure my previous function, similarly to what I did with the Fibonacci function, in an iterative manner. This step is meant to ensure that indeed, for certain operations, WebAssembly will be faster than JavaScript:

Go:

func slowV2() {
	for i := 0; i <= 6; i++ {
		slow(i)
	}
}

JavaScript:

function slowV2() {
    for (let i = 0; i <= 6; i++) {
        slow(i);
    }
}

And these were the results:

Go-WebAssemblyJavaScript
slowV2(6)8.266 [s]10.518 [s]

No surprises here as well. The results remain the same where WebAssembly performs better than JavaScript.

Conclusions

Concluding graph
  • JavaScript is fast – years of optimizing JavaScript are at it’s back.
  • WebAssembly supplies similar results to JavaScript execution time under certain conditions and around 30% better than JavaScript for others.
  • For most applications, WebAssembly is not a silver bullet in terms of performance. Your application should really have specific characteristics in order to justify the usage of WebAssembly.

Main Takeaway

If performance is the reason for which you want to develop using WebAssembly, first benchmark the main logic of your application and see if it really provides a meaningful advantage.

Feel free to comment, like and share. See you on the next one!

Leave a Reply