go: cmd/compile: wasm code causes out of memory error on Chrome and Firefox for Android
I am very excited that Go ships now with Webassembly support. I ran wasm code generated by Go 1.11 on Chrome and Firefox on desktops (MacOS and Linux) and on Chrome, Firefox and Safari on iOS devices. Running Go generated wasm code on Android devices failed though.
Minimal example
- Go code
GOOS=js GOARCH=wasm go build -o test.wasm wasm.go
package main
import (
"fmt"
)
func main() {
fmt.Println("hello")
}
index.html
wasm_exec.js
fromgo/misc/wasm
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
(async function() {
const wasmFile = "test.wasm"
let run
const go = new Go()
try {
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmFile), go.importObject)
document.querySelector('#info').innerHTML = "ready"
run = go.run(instance)
} catch (err) {
document.querySelector('#info').innerHTML = err
console.log(err)
}
})()
</script>
<div id="info"></div>
</body>
</html>
- files are served via Nginx using adjusted
mime.types
Expected behavior
- page displays “ready” after the wasm file has been loaded and the console shows “hello”
- this works on Desktops and iOS devices
Actual behavior
- on Android devices the above code fails with “RangeError: WebAssembly Instantiation: Out of memory: wasm memory” (Chrome) and “out of memory” (Firefox)
- tested on Chrome for Android (68.0.3440.91) and Firefox for Android (61.0.2). Several devices from different manufacturers were tested
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 7
- Comments: 36 (7 by maintainers)
Commits related to this issue
- Basic test benchmark for WebAssembly integration. This is not nearly as efficient as a native build of the transform binary, due to bugs such as http://crbug.com/853685 and https://github.com/golang/... — committed to twifkak/amppackager by twifkak 6 years ago
- Basic test benchmark for WebAssembly integration. (#216) This is not nearly as efficient as a native build of the transform binary, due to bugs such as http://crbug.com/853685 and https://github.co... — committed to ampproject/amppackager by twifkak 6 years ago
- Update github pages demo for go 1.12+ - go 1.12 optimizes memory allocation for wasm - see https://github.com/golang/go/issues/27462. This was causing the demo to OOM on memory restricted devices... — committed to niklasfasching/gowen by niklasfasching 5 years ago
- Update github pages demo for go 1.12+ - go 1.12 optimizes memory allocation for wasm - see https://github.com/golang/go/issues/27462. This was causing the demo to OOM on memory restricted devices... — committed to niklasfasching/go-org by niklasfasching 5 years ago
- Update github pages demo for go 1.12+ - go 1.12 optimizes memory allocation for wasm - see https://github.com/golang/go/issues/27462. This was causing the demo to OOM on memory restricted devices... — committed to niklasfasching/go-org by niklasfasching 5 years ago
Change https://golang.org/cl/170950 mentions this issue:
runtime, cmd/link: optimize memory allocation on wasm
I hacked on the runtime library to reduce the initial allocation to ~80MB: https://github.com/golang/go/compare/master...twifkak:small
A couple of notes:
growMemory
and then lowered some constants.wams
tool after compilation, then you don’t need to build a custom Go compiler. Only patch yourGOROOT
’s runtime dir, which will get compiled into the wasm binary.println
s from my patch.Update: It seems this is bad for processes that create a lot of flyweight objects.
runtime/mem_js.go
needs a free list or some such. Update 2: I wrote a free list. It requiresGODEBUG=gcstoptheworld=1
.I wrote a small tool that can patch the memory section of a
.wasm
binary. This allows for easy experimenting with smaller initial page sizes without building a modified tool chain. It seems as if during instantiation quite a bit of memory is allocated by the WebAssembly runtime. When starting with 4096 pages (256MB) the runtime grows the memory on my desktop machine to 745865216 bytes (about 710MB, more than 11000 pages). As I have trouble allocating more than 7500 pages on my Android devices, this approach alone won’t help to make Go generated.wasm
binaries run on Android. I am surprised that the instantiation is that expensive but I can understand now why the initial memory was set to 1GB …I updated my fork to implement a free list, so that Go can reclaim freed memory in wasm. Using this, combined with
GODEBUG=gcstoptheworld=1
, I was able to run a pretty allocation-intensive workload (parsing a bunch of HTML files) with <124MB. (Using the concurrent GC, it crashes.)Update: With
GOGC=20
(arbitrary first guess), memory is <80MB.@twifkak
Here is the article - https://medium.com/@martinolsansky/webassembly-with-golang-is-fun-b243c0e34f02
And my
/src
https://github.com/olso/go-wasm-cat-game-on-canvas-with-dockerThank you again! 😻
Using some of the optimization flags mentioned here, I got my .wasm file down to ~2.6MB but still seeing OOM errors on everything but my mac 😦 I haven’t tried the fork mentioned above (I’m very new to Go) but I did try using ‘bytecoder’ with some Java for comparison…
That generates a 15KB file (also interacting with Javascript / canvas 2D) and runs great on Android g-tab2, and even an old Nexus 5 Android 6 device! and obviously the load times are vastly improved. Although still no success on my iOS devices, but that might be because I keep them on older iOS versions for development testing.
I know that wasm support is still experimental, but could this issue possibly be upgraded from ‘Performance’ ? Since currently this makes Go WASM unusable in many situations, and this probably needs fixing before WASI hits mainstream. Thanks.
It works. Thank you! @twifkak
Why does WebAssembly allocate this 1GB of (mostly unused) memory in the first place? Is there a way to limit the amount of memory it can request? (
WebAssembly.Memory({initial: x, maximum: y})
comes to mind but my attempts to populate the importObject with preallocated memory did not succeed.) Edit: Dumping a wasm file with wasm-dump shows that memory is indeed set to 16384 64kB pages (=1GB):memory[0] pages: initial=16384
. I am wondering whether this can be changed to a more reasonable size.Given the market share of Android devices, this would be a major drawback …