WebAssembly / Go(tinygo)函数执行时间非常慢

huangapple go评论76阅读模式
英文:

WebAssembly / Go (tinygo) function execution time extremely slow

问题

我使用tinygo将Go代码编译为WebAssembly,但不明白为什么该函数执行需要17分钟,而相同的JavaScript函数只需要4000毫秒。我做错了什么?另外,有可能修改Go中的函数,而不必使用"syscall/js"吗?我尝试使用函数导出,但无法将包含不同类型的数组返回给JavaScript。

以下是你的Go代码:

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)
}

使用以下命令编译:

tinygo build -o tinygo.wasm -target wasm ./main.go

以下是JavaScript代码:

<!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>

你需要将文件wasm_exec.js从你的..\tinygo\0.25.0\targets\目录复制过来。

tinygo版本为0.25.0,使用的是go版本go1.19.1和LLVM版本14.0.0。

英文:

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 (
&quot;fmt&quot;
&quot;math&quot;
&quot;syscall/js&quot;
)
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 &gt;= 2 || maxIteration &lt;= n {
break
}
}
arr := []interface{}{n, d &lt;= 2}
return arr
}
func main() {
fmt.Println(&quot;TinyGo&quot;)
js.Global().Set(&quot;mandelbrotTinyGo&quot;, js.FuncOf(mandelbrotTinyGo))
&lt;-make(chan bool)
}

Compiled with:

tinygo build -o tinygo.wasm -target wasm ./main.go

JavaScript code:

&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Title&lt;/title&gt;
&lt;script src=&quot;wasm_exec.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
const go = new Go();
const WASM_URL = &#39;tinygo.wasm&#39;;
var wasm;
if (&#39;instantiateStreaming&#39; in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =&gt;
resp.arrayBuffer()
).then(bytes =&gt;
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;button id=&quot;btnDraw&quot;&gt;Draw&lt;/button&gt;
&lt;canvas id=&quot;myCanvas&quot; style=&quot;display: block; margin-left: auto;margin-right: auto;&quot;&gt;
&lt;/canvas&gt;
&lt;script&gt;
const MAX_ITERATION = 80
var canvas = document.getElementById(&#39;myCanvas&#39;)
var ctx = canvas.getContext(&#39;2d&#39;)
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) =&gt; i === 0 ? &#39;#000&#39; : &#39;#&#39; + Math.random().toString(16).substr(2, 6))
function draw() {
for (let i = 0; i &lt; WIDTH; i++) {
for (let j = 0; j &lt; 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 &lt;= 2 &amp;&amp; n &lt; MAX_ITERATION)
return [n, d &lt;= 2]
}
function start(){
let startTime = performance.now()
draw()
let endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`)
}
myButton = document.getElementById(&quot;btnDraw&quot;);
myButton.addEventListener(&quot;click&quot;, function() {
start();
});
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

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)

答案1

得分: 1

我在使用TinyGo时遇到了同样的问题。我的解决方案是使用原生的'go build'而不是TinyGo。这样可以生成一个更快但体积较大的Wasm模块。

不幸的是,在我的情况下,与原生的JavaScript相比,性能还不如预期。这可能是因为编译成Wasm时缺少了一些优化,因为在本地运行go程序比本地JavaScript/Node.js版本要快得多。

英文:

I had the same issue with TinyGo. My solution was to use the native 'go build' instead of TinyGo. That produces a faster Wasm module but with a larger size.

Unfortunately, in my case, the performance it's not yet better compared with native Javascript. It might be missing optimizations when compiling to Wasm since running the go program natively is much faster than the Native Javascript/Nodejs version.

答案2

得分: 1

你试过-opt=2了吗?这非常重要。

请参考https://tinygo.org/docs/reference/usage/important-options

英文:

Did you try -opt=2 ? It's fairly important.

See https://tinygo.org/docs/reference/usage/important-options

huangapple
  • 本文由 发表于 2022年9月20日 10:12:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/73780950.html
  • go
  • javascript
  • performance
  • tinygo
  • webassembly

如何在 :?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定