この記事は Wano Group Advent Calendar 2022 の8日目の記事です。
概要
GoをWebブラウザ上で動作させるために、色々な方法やツールはありますが、今回は正規表現のパターン判断を作るの例として、WebAssemblyとGopherJS二つのやり方で紹介と比較したいと思います。
WebAssemblyとGopherJS
-
WebAssembly(略称Wasm)は、Webブラウザを含むモダンな実行環境での効率的なコード実行ために、アセンブリ言語ライクなテキスト形式で設計された低レベルフォーマットだ。GO言語使いたい場合、公式のリポジトリからwasmファイルをロードするツール(wasm_exec.js)を提供します
-
一方、GopherJSはGoのコードをJavaScriptに変換するライブラリです。そのほか、コンパイラ以外の機能もCLIで提供。
GOの実装
今回は簡単な正規表現(英数のみの文字を判断)を例として実装します。
WebAssembly:
//go:build wasm
// +build wasm
package main
import (
"regexp"
"syscall/js"
)
// 正規表現の共通関数
func IsStringOnlyIncludeAlphaNum(this js.Value, args []js.Value) interface{} {
var ValueCheck = regexp.MustCompile("^[0-9a-zA-Z_]+$").MatchString
return ValueCheck(args[0].String())
}
func main() {
js.Global().Set("IsWasmStringOnlyIncludeAlphaNum", js.FuncOf(IsStringOnlyIncludeAlphaNum))
select {} // avoid exit
}
compileしてwasmの生成
GOOS=js GOARCH=wasm go build -o main.wasm wasm_main.go
次はGopherJSのGO実装:
//go:build js
// +build js
package main
import (
"github.com/gopherjs/gopherjs/js"
"regexp"
sysjs "syscall/js"
)
// 正規表現の共通関数
func IsStringOnlyIncludeAlphaNum(this sysjs.Value, args []sysjs.Value) interface{} {
var ValueCheck = regexp.MustCompile("^[0-9a-zA-Z_]+$").MatchString
return ValueCheck(args[0].String())
}
func main() {
js.Global.Set("IsGopherStringOnlyIncludeAlphaNum", sysjs.FuncOf(IsStringOnlyIncludeAlphaNum))
}
gopherjsのコマンドがパッケージをビルドして生成する
gopherjs build -o gopherjs_test.js gopherjs_main.go
フロント側
bodyだけ表示
<body>
<h1>WebAssemblyの場合</h1>
<script src="./wasm_exec.js"></script>
<script>
const go = new Go()
WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject).then(result => {
go.run(result.instance);
})
async function run() {
const text = document.getElementById("testStr").value;
console.log("文字が英数のみ:"+self.IsWasmStringOnlyIncludeAlphaNum(text));
}
</script>
<input type="text" id="testStr" name="testStr">
<button onClick='run()' id="buttonCheck">確認</button>
</body>
<body>
<h1>GopherJSの場合</h1>
<script src="./gopherjs_test.js"></script>
<script>
async function run() {
const text = document.getElementById("testStr").value;
console.log("文字が英数のみ:"+self.IsGopherStringOnlyIncludeAlphaNum(text));
}
</script>
<input type="text" id="testStr" name="testStr">
<button onClick='run()' id="buttonCheck">確認</button>
</body>
HTTPサーバを起動する (両方共通)
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
fmt.Print("start server...")
http.Handle("/", http.FileServer(http.Dir("")))
log.Fatal(http.ListenAndServe(":8080", nil))
}
$ go run server.go
結果表示
両方の速度比較
performanceを使って確認
async function run() {
const text = document.getElementById("testStr").value;
let startTime1 = performance.now();
self.IsGopherStringIncludeAlphaNum(text);
let startTime2 = performance.now();
const go = new Go()
WebAssembly.instantiateStreaming(fetch('main.wasm'), go.importObject).then(result => {
go.run(result.instance);
self.IsStringIncludeAlphaNum(text);
let startTime3 = performance.now();
console.log("GopherJS (キャッシュなし): ",startTime2-startTime1);
console.log("wasm(インスタンス化時間を含め): ",startTime3-startTime2);
let startTime4 = performance.now();
self.IsStringIncludeAlphaNum(text);
let startTime5 = performance.now();
self.IsGopherStringIncludeAlphaNum(text);
let startTime6 = performance.now();
console.log("wasm: ",startTime5-startTime4);
console.log("GopherJS: ",startTime6-startTime5);
})
}
テスト環境 : Chrome
バージョン : 107.0.5304.121(Official Build) (x86_64)
項目 / 文字サイズ | 10byte | 200kb | 1MB | 10MB |
---|---|---|---|---|
Wasm | 0.20ms | 1.39ms | 9.79ms | 102.69ms |
GopherJS | 1.29ms | 1.80ms | 5.70ms | 39.10ms |
Wasm (インスタンス化時間を含め) | 23.80ms | 38.60ms | 46.79ms | 1951.80ms |
GopherJS (キャッシュなし) | 9.10ms | 10.79ms | 12.60ms | 46.09ms |
まとめ
複雑な計算処理に関しては、Wasmの処理速度が早いですか、文字処理など通常のWeb開発の場合、GopherJSでも劣らないと思います。