WebAssembly is a low-level, assembly-like language that brings near-native performance to browsers. WebAssembly allows developers to code in their familiar programming languages such as C/C++, Rust, Go, Python, and so on. The code is then compiled into the wasm binary format and can be executed in browsers .
WebAssembly was first released in 2017 and remains a rather advanced topic in web development today. At the time of writing this post, the usage of WebAssembly is still less than 0.1% across all websites(W3Techs).
WebAssembly support in Golang
Go 1.11 added an experimental port to WebAssembly. Go 1.12 has improved some parts of it, with further improvements expected in Go 1.13. Go 1.21 added a new port targeting the WASI syscall API.
For Go 1.23 and earlier, the wasm support files needed in this article are located in misc/wasm
, and the path should be replaced when performing operations with files such as lib/wasm/wasm_exec.js
.
Fibonacci calculations example of JavaScript and Golang WASM
The primary goal of WebAssembly is to provide high performance in modern web applications, but how much faster could it be compared to JavaScript? Let’s do a simple comparison by running Fibonacci calculations with both JavaScript and Go-WebAssembly examples.
Golang WebAssembly
The main.go
file:
package main
import "syscall/js"
func fib(n int) int { // function that computes the nth Fibonacci number recursively.
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
func fibJsWrap(this js.Value, inputs []js.Value) any { // wraps up the fib function via WASM
return js.ValueOf(fib(inputs[0].Int()))
}
func main() {
c := make(chan struct{}, 0)
js.Global().Set("goFib", js.FuncOf(fibJsWrap)) // Expose goFib to JS
<-c // blocks forever to keep the WASM instance alive
}
Compile it with the go build
command:
GOOS=js GOARCH=wasm go build -o fib.wasm
HTML page
A <pre>
element for displaying calculation results plus two buttons for initiate calculations:
<h1>Fibonacci sequence calculation</h1>
<div>
<pre id="result">
</pre>
<button onclick="calFib(false)">Javascript version</button>
<button onclick="calFib(true)">Go version</button>
</div>
JavaScript
Write the jsFib
function:
const jsFib = (n) => {
if ([0, 1].includes(n)) {
return n
}
return jsFib(n - 1) + jsFib(n - 2)
}
Import the Go-WASM dependency to the HTML page before the JS code. The dependency can be found at lib/wasm/wasm_exec.js
under the Go root(go env GOROOT
).
<script src="/wasm_exec.js"></script>
Initialise the Go-WASM instance:
const go = new Go();
WebAssembly.instantiateStreaming(fetch("/fib.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});
The click-event handler shall record the duration of each calculation and display the calculation result in the <pre></pre>
element. To enlarge the performance difference, set the factorisation to 45 times.
const calFib = (go) => {
let startTime = Date.now() / 1000;
let fib;
if (go) {
fib = goFib(40); // sync call to Go WASM function
} else {
fib = jsFib(40);
}
const label = go ? "Golang" : "JavaScript";
const result = `${label} 40 times of Fibonacci calculations\nresult: ${fib}\n${((Date.now() / 1000) - startTime).toFixed(2)}s`;
document.getElementById("result").textContent = result;
}
Result
The Go-wasm version is 4.52 seconds faster than the JavaScript version:
Although, the compiled wasm file sizes 1.7Mb, it could be further shrunk if you use tinygo
or gzip
.