I compiled a go code with tinygo to WebAssembly and I don't understand why the function took 17 minutes to execute while the same function in JavaScript only took 4000 ms. what I'm doing wrong? also it is possible to modify the function in Go so I don't have to use "syscall/js"? I tried using function exports but couldn't return the array with different types to JavaScript.
Here is my Go code:
package main
import (
"fmt"
"math"
"syscall/js"
)
type p struct {
x float64
y float64
}
type z struct {
x float64
y float64
}
func mandelbrotTinyGo(_ js.Value, args []js.Value) interface{} {
maxIteration := args[0].Int()
var newZ = z{0, 0}
var newP = p{0, 0}
n := 0
cx := args[1].Float()
cy := args[2].Float()
d := 0.0
for {
newP = p{math.Pow(newZ.x, 2) - math.Pow(newZ.y, 2), 2 * newZ.x * newZ.y}
newZ = z{newP.x + cx, newP.y + cy}
d = 0.5 * (math.Pow(newZ.x, 2) + math.Pow(newZ.y, 2))
n += 1
if d >= 2 || maxIteration <= n {
break
}
}
arr := []interface{}{n, d <= 2}
return arr
}
func main() {
fmt.Println("TinyGo")
js.Global().Set("mandelbrotTinyGo", js.FuncOf(mandelbrotTinyGo))
<-make(chan bool)
}
Compiled with:
tinygo build -o tinygo.wasm -target wasm ./main.go
JavaScript code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
const WASM_URL = 'tinygo.wasm';
var wasm;
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
</script>
</head>
<body>
<button id="btnDraw">Draw</button>
<canvas id="myCanvas" style="display: block; margin-left: auto;margin-right: auto;">
</canvas>
<script>
const MAX_ITERATION = 80
var canvas = document.getElementById('myCanvas')
var ctx = canvas.getContext('2d')
const WIDTH = window.innerWidth
const HEIGHT = window.innerHeight
ctx.canvas.width = WIDTH
ctx.canvas.height = HEIGHT
const REAL_SET = { start: -2, end: 1 }
const IMAGINARY_SET = { start: -1, end: 1 }
const colors = new Array(16).fill(0).map((_, i) => i === 0 ? '#000' : '#' + Math.random().toString(16).substr(2, 6))
function draw() {
for (let i = 0; i < WIDTH; i++) {
for (let j = 0; j < HEIGHT; j++) {
complex = {
x: REAL_SET.start + (i / WIDTH) * (REAL_SET.end - REAL_SET.start),
y: IMAGINARY_SET.start + (j / HEIGHT) * (IMAGINARY_SET.end - IMAGINARY_SET.start)
}
//Call the JS function
//const [m, isMandelbrotSet] = mandelbrot(complex)
//Call the WebAssembly/tinyGo function
const [m, isMandelbrotSet] = mandelbrotTinyGo(MAX_ITERATION, complex.x, complex.y)
ctx.fillStyle = colors[isMandelbrotSet ? 0 : (m % colors.length - 1) + 1]
ctx.fillRect(i, j, 1, 1)
}
}
}
function mandelbrot(c) {
let z = { x: 0, y: 0 }, n = 0, p, d;
do {
p = {
x: Math.pow(z.x, 2) - Math.pow(z.y, 2),
y: 2 * z.x * z.y
}
z = {
x: p.x + c.x,
y: p.y + c.y
}
d = Math.sqrt(Math.pow(z.x, 2) + Math.pow(z.y, 2))
n += 1
} while (d <= 2 && n < MAX_ITERATION)
return [n, d <= 2]
}
function start(){
let startTime = performance.now()
draw()
let endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`)
}
myButton = document.getElementById("btnDraw");
myButton.addEventListener("click", function() {
start();
});
</script>
</body>
</html>
The file wasm_exec.js must to be copied from your ..\tinygo\0.25.0\targets\ directory
tinygo version 0.25.0 windows/amd64 (using go version go1.19.1 and LLVM version 14.0.0)