WASM 的概念,这几年还是挺火的,新的语言,比如 Rust、Go、Swift 等,都对 WASM 提供支持。相比之下,Go 语言的简单性,使得对 WASM 的支持,使用起来也较简单。本文是目前公开资料中为数不多较完整的教程,希望能对你有帮助。
WASM 是什么
标题说:“Golang 中的 Wasm 太棒了。”,但请用几句话来说“Wasm”是什么? WebAssembly 主页说:“WebAssembly(缩写为 Wasm)是一种基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为编程语言的可移植编译目标,支持在 Web 上部署客户端和服务器应用程序。” 总结就是:
- “Wasm 是一种可移植的格式(如 Java 或 .Net),你可以在任何有支持它的主机的地方执行它。最初,主要的主机是带有浏览器的 JavaScript”。
现在,你可以用 JavaScript 和 NodeJS 运行 Wasm,我们最近看到了像 Wasmer 项目这样的 Wasm 运行时的诞生,允许在任何地方运行 Wasm。 我喜欢说“一个 wasm 文件就像一个容器镜像,但更小,没有操作系统”。
你可以用多种语言编译一个 Wasm 文件:C/C 、Rust、Golang、Swift ……我们甚至看到了专门用于构建 Wasm 的语言的出现,比如 AssemblyScript[1] 或有前途的 Grain[2](可以密切关注它,语法很可爱)。 今年夏天,我决定开始使用 Wasm。这种趋势似乎是使用 Rust,但我很快就明白我的小步骤会很复杂。困难不一定来自语言本身。最乏味和困难的部分是我在浏览器中运行一个简单的“Hello World”所需的所有工具。经过一番搜索,我发现 Golang 为 Wasm 提供了非常简单的支持(比 Rust 简单得多)。所以,我的假期作业是用 Golang 完成的。 Golang 对 Wasm 的支持非常棒。通常,WebAssembly 有四种数据类型(32 和 64 位整数,32 和 64 位浮点数),使用带有字符串参数(甚至 JSON 对象)的函数可能会很混乱。幸运的是,Go 提供了wasm_exec.js 与 JavaScript API 交互的文件。
WASM 在Go中的简单示例
创建项目:
代码语言:javascript复制 go-wasm/
|__ out/
|__ go/
|__ main.go
main.go:
代码语言:javascript复制package main
func main() {
println("Hello World!!!")
}
运行 go 文件go run go/main.go。这将打印Hello World。
现在我们可以将它编译成 WebAssembly 模块并作为 WebAssembly 运行:
代码语言:javascript复制GOOS=js GOARCH=wasm go build -o out/main.wasm go/main.go
这将在out文件夹里生成main.wasm。与 Rust 不同,Golang 不会生成任何绑定文件。
拷贝wasm绑定文件wasm_exec.js
到out目录中。
# go原生版本
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./out
在out目录中创建一个index.html
文件并添加以下内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Go wasm</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
const go = new Go()
WebAssembly.instantiateStreaming(
fetch('main.wasm'),
go.importObject
).then((res) => {
go.run(res.instance)
})
</script>
</body>
</html>
也可以用这种更健壮的版本:
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Go wasm</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
// This is a polyfill for FireFox and Safari
if (!WebAssembly.instantiateStreaming) {
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer()
return await WebAssembly.instantiate(source, importObject)
}
}
// Promise to load the wasm file
function loadWasm(path) {
const go = new Go()
return new Promise((resolve, reject) => {
WebAssembly.instantiateStreaming(fetch(path), go.importObject)
.then(result => {
go.run(result.instance)
resolve(result.instance)
})
.catch(error => {
reject(error)
})
})
}
// Load the wasm file
loadWasm("main.wasm").then(wasm => {
console.log("main.wasm is loaded ")
}).catch(error => {
console.error(error)
})
</script>
</body>
</html>
最重要的部分是: 这行代码 和这一行WebAssembly.instantiateStreaming,它是允许加载 wasm 文件的 JavaScript API。
index.html加载wasm_exec.js文件。请注意,此文件适用于 Browser 和 NodeJS 环境,它导出一个Go对象。
然后我们添加一个本地脚本。在脚本中,我们执行以下操作:
- Instantiate Go from the wasm_exec.js.
- Fetch the WebAssembly Module using instantiateStreaming
接下来启动一个Web server尝试一下go wasm。
./webserver.go:
package main
import (
"log"
"net/http"
"strings"
)
const dir = "./out"
func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " dir " on http://localhost:8080")
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Cache-Control", "no-cache")
if strings.HasSuffix(req.URL.Path, ".wasm") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
项目结构:
代码语言:javascript复制├── go
│ └── main.go
└── out
├── index.html
├── wasm_exec.js
└── main.wasm
├── webserver.go
运行:
代码语言:javascript复制go run webServer.go
打开http://localhost:8080 ,可以在console上显示Hello World!!!:
注意: 由于wasm是在网页上实时加载,所以生成的 WebAssembly 模块的文件大小非常重要。上例中仅仅一个 Hello World 就高达 1.3MB,对于现代网页来说是完全不能接受的。
代码语言:javascript复制-rw-r--r-- 1 sendilkumar staff 482B Jul 5 23:20 index.html
-rwxr-xr-x 1 sendilkumar staff 1.3M Jul 5 23:19 main.wasm
-rw-r--r-- 1 sendilkumar staff 13K Jul 5 23:18 wasm_exec.js
或许WebAssembly能提供更优秀的性能,但如果引入的模块过大的话就不应当考虑它。
别当心,我们有Tiny GO!
TinyGO 使用
TinyGo 是一个将 Golang 带入微控制器和现代网络浏览器的项目。他们有一个基于 LLVM 编译的全新编译器。使用 TinyGo,我们可以生成经过优化以在芯片中执行的微小库。
TinyGo 允许为微控制器编译 Golang 源代码,它也可以将 Go 代码编译为 Wasm。TinyGo 是一个用于“小地方”的编译器,因此生成的文件要小得多。
查看如何安装tinygo:https://tinygo.org/getting-started/
安装后,您可以使用 TinyGo 编译任何 Golang 代码。我们可以使用以下命令将 Golang 编译成 WebAssembly 模块。
代码语言:javascript复制tinygo build -o out/main.wasm -target wasm ./go/main.go
使用tinygo编译后,wasm模块减小到3.8K:
代码语言:javascript复制-rw-r--r-- 1 sendilkumar staff 482B Jul 5 23:20 index.html
-rwxr-xr-x 1 sendilkumar staff 3.8K Jul 5 23:29 main.wasm
-rw-r--r-- 1 sendilkumar staff 13K Jul 5 23:18 wasm_exec.js
使用tinygo编译的wasm模块,在加载的时候也要搭配tinygo版本的wasm_exec.js:
代码语言:javascript复制cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" ./out/wasm_exec.js
# 或者从这里下载
https://github.com/tinygo-org/tinygo/blob/master/targets/wasm_exec.js
再重新启动webserver尝试下吧!
References
Go 中的 WASM 很棒:全网最全示例教程 tinygo快速入门 如何使用WebAssembly提升性能? 从首届 WebAssembly Summit 看 Wasm 未来发展方向 [
](https://blog.51cto.com/u_15057848/2567467)