Go 1.11でgo build
のtargetに
GOOS=js
GOARCH=wasm
を指定できるようになって久しいですが、皆さんこちらお使いでしょうか?
Goが出力するWebAssemblyのファイルサイズが気になり、 これを計測するためのリポジトリ、go-wasm-sizesを作ったので、こちらと合わせて実際の計測結果を紹介します。
wasmのサイズ計測
やっている事は単純で、
- Goの標準パッケージのリストを書き出す
- それらをimportだけして使用しないコードを生成する
- 全てwasmにビルドする
作業を行っているだけです。
コード例
package main import _ "fmt" func main() {}
また、比較対象として、何のpackageもimportしていないパターンも用意しました。
計測方法について追記 (2018/12/12)
_で参照した場合、グローバル変数として定義されている構造体のメソッドとか、initで参照されているものは入るけど、code elimiminationでカットされてたりするはずなので、実際に使うともっと大きくなったりする印象あるけどwasm方式だとどうなんだろうか? https://t.co/h8NEXLK9H2
— 渋川よしき (@shibu_jp) December 12, 2018
渋川さんからご指摘いただきましたが、今回の計測方法だとdead code eliminationされている可能性がありそうなので、こちらも考慮した上で別途検証したいです。
計測結果
よく使いそうなpackageを適当に選んで紹介します。 計測対象にしている全packageのリストは、上記リポジトリのREADME.mdに記載があります。
※net/httpなどの子packageは、作業の簡略化のために /
ではなく _
で連結した名前を設定しています。
1.30MB blank/blank.wasm # This package only contains func main(). 1.30MB math/math.wasm 1.30MB strconv/strconv.wasm 1.31MB io/io.wasm 1.34MB syscall_js/syscall_js.wasm 1.43MB time/time.wasm 1.44MB unicode/unicode.wasm 1.45MB strings/strings.wasm 1.45MB bytes/bytes.wasm 1.45MB path/path.wasm 1.46MB bufio/bufio.wasm 1.50MB os/os.wasm 1.98MB reflect/reflect.wasm 1.98MB sort/sort.wasm 2.00MB regexp/regexp.wasm 2.15MB fmt/fmt.wasm 2.16MB net_url/net_url.wasm 2.16MB context/context.wasm 2.37MB net/net.wasm 7.22MB net_http/net_http.wasm 11.32MB net_http_pprof/net_http_pprof.wasm
わかったこと
- 何もimportしなくてもwasmのサイズは1.3MBになり、一般的なJSファイルと比べるとかなり大きい。
- fmtをimportするとサイズが +800KB となり意外と大きく(恐らく内部でreflectを使っているため)、手軽に使うには向かない。
builtin
のprintln()
が JSコンソールに表示を行うので、Goで実行した内容をコンソールに出したいだけであればそちらを使うのがよさそう。
- strconv, math等はほとんどサイズに影響を及ぼさない。何もimportしない状態で既にビルドに含まれているのかも?
一番大きかったpackageは net/http/pprof
で、なんと 11.32MB になりました。
圧縮するとどうなる?
生のままだとファイルサイズが大きすぎるため、実際にGoのwasmを使用する場合は、ブラウザがサポートする形式で圧縮して送信するのが現実的な方法となりそうです。
今回は、Gzip, Brotliの2つで、圧縮結果のサイズを比較してみました。
Gzip
287KB blank/blank.wasm.gz # This package only contains func main(). 287KB math/math.wasm.gz 287KB strconv/strconv.wasm.gz 290KB io/io.wasm.gz 295KB syscall_js/syscall_js.wasm.gz 307KB time/time.wasm.gz 322KB unicode/unicode.wasm.gz 323KB os/os.wasm.gz 324KB strings/strings.wasm.gz 324KB bytes/bytes.wasm.gz 324KB path/path.wasm.gz 325KB bufio/bufio.wasm.gz 414KB reflect/reflect.wasm.gz 414KB sort/sort.wasm.gz 418KB regexp/regexp.wasm.gz 444KB fmt/fmt.wasm.gz 445KB net_url/net_url.wasm.gz 446KB context/context.wasm.gz 497KB net/net.wasm.gz 1617KB net_http/net_http.wasm.gz 2455KB net_http_pprof/net_http_pprof.wasm.gz
Brotli
222KB blank/blank.wasm.br # This package only contains func main(). 222KB math/math.wasm.br 223KB strconv/strconv.wasm.br 224KB io/io.wasm.br 228KB syscall_js/syscall_js.wasm.br 237KB time/time.wasm.br 247KB unicode/unicode.wasm.br 249KB os/os.wasm.br 249KB strings/strings.wasm.br 249KB bytes/bytes.wasm.br 249KB path/path.wasm.br 250KB bufio/bufio.wasm.br 312KB reflect/reflect.wasm.br 313KB sort/sort.wasm.br 316KB regexp/regexp.wasm.br 336KB fmt/fmt.wasm.br 336KB net_url/net_url.wasm.br 336KB context/context.wasm.br 375KB net/net.wasm.br 1768KB net_http_pprof/net_http_pprof.wasm.br
わかったこと
最小のblank.wasm, 最大のnet_http_pprof.wasmでそれぞれ比較すると、
blank.wasm
- Gzip: 22% (287KB)
- Brotli: 17% (222KB)
net_http_pprof.wasm
- Gzip: 22% (2455KB)
- Brotli: 16% (1768KB)
となっており、どちらの圧縮法を使っても20%程度にはなるようです。 これだけファイルサイズが変わるのであれば、体感のロード時間は大きく変わるので、やはりGoのwasmの配信は圧縮した上で行うのが基本的な方針になりそうです。
Go + wasmで実際に何か作られる際にぜひ参考にしてください!