英文:
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 (
"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)
答案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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论