正規表現の例として、WebAssemblyとGopherJSを比較する


この記事は [Wano Group Advent Calendar 2022](https://qiita.com/advent-calendar/2022/wano-group) の8日目の記事です。


## 概要

GoをWebブラウザ上で動作させるために、色々な方法やツールはありますが、今回は正規表現のパターン判断を作るの例として、WebAssemblyとGopherJS二つのやり方で紹介と比較したいと思います。

## WebAssemblyとGopherJS

- WebAssembly（略称Wasm）は、Webブラウザを含むモダンな実行環境での効率的なコード実行ために、アセンブリ言語ライクなテキスト形式で設計された低レベルフォーマットだ。GO言語使いたい場合、公式のリポジトリからwasmファイルをロードするツール(wasm_exec.js)を提供します
    - https://github.com/golang/go/tree/master/misc/wasm

- 一方、GopherJSはGoのコードをJavaScriptに変換するライブラリです。そのほか、コンパイラ以外の機能もCLIで提供。
    - https://github.com/gopherjs/gopherjs

## GOの実装

今回は簡単な正規表現(英数のみの文字を判断)を例として実装します。

WebAssembly：
```golang / wasm_main.go
//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実装：
```golang / gopherjs_main.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だけ表示

```html
<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> 
```

```html
<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サーバを起動する (両方共通)

``` server.go
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```


## 結果表示

![スクリーンショット 2022-11-30 0.45.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153285/90a1ccde-318a-b519-ed59-7527e401121e.png)
![スクリーンショット 2022-11-30 0.46.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/153285/cd7a1795-5dd6-f8b2-6087-b86e728efd76.png)


### 両方の速度比較

performanceを使って確認
```js
        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でも劣らないと思います。