WebAssembly / Go (tinygo) function execution time extremely slow

Viewed 39

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)

0 Answers
Related