Unity 2021 の Binaryen を Apple Silicon 版に置き換えてみた話

え、ISUCON12本選の話はって?まあもうちょっと待ってください…。

最近 Unity の WebAssembly ビルドで遊んでいたのですが、なんか incremental build って言うわりに結構遅く、どこが遅いんだろうなあと思ってアクティビティモニタを開いたら、wasm-opt が CPU をめっちゃ食っていて、しかもそれが Intel バイナリでした (エディタは Apple Silicon 版なのに)!

というわけでこれを Apple Silicon 版バイナリに置き換えるだけで早くなるんじゃないか?と思ったのでやってみようという話です。

私は普通に Unity Hub で Unity 2021.3.8f1 (執筆時の Unity 2021.x 最新版) と WebGL Build Support を落としてきているので、binaryen は /Applications/Unity/Hub/Editor/2021.3.8f1/PlaybackEngines/WebGLSupport/BuildTools/Emscripten/binaryen/bin にあります。

./wasm-opt --version してみると

wasm-opt version 101 (version_101-30-g5387a0b1f)

とのこと。binaryenがローカル変更ある場合に -dirty を付けるか (≒何かUnity独自パッチが入っているか) は定かではありませんが、初手としては適当にそのへんのバイナリを持ってきて動いてみないか試してみるのが早そうです。

とりあえずコミットハッシュ 5387a0b1f で検索すると、次のコミットが見つかりました。

https://github.com/WebAssembly/binaryen/commit/5387a0b1ffcaf925b40ee661063893c475d4c632

だめだった: 既存バイナリを使わせてみる

コミットハッシュでそのまま本家のリポジトリが出てくる≒Unity独自の改変はない (おそらく) 、ということなので、適当に新しめの binaryen から持ってきても動くのではないか、と推察して brew install binaryen して入った wasm-opt version 109 をとりあえずそのまま使ってみました (ネタバレ: これではだめでした)。

mv wasm-opt wasm-opt.orig で退避して ln -s $(which wasm-opt) ./wasm-optシンボリックリンク

これでうまくいくかな…と思いきや emcc2: warning: unexpected binaryen version: 109 (expected 101) で怒られました。

仕方ないのでシンボリックリンクはやめて、このようなシェルスクリプトを用意してみました:

#!/bin/bash

ACTUAL_WASM_OPT=/opt/homebrew/bin/wasm-opt # replace with your `which wasm-opt`

if [[ "$1" == "--version" ]]; then
    echo "wasm-opt version 101 (compatible; $("$ACTUAL_WASM_OPT" --version))"
    exit 0
fi

exec "$ACTUAL_WASM_OPT" "$@"

しかしこれでもだめでした。

Building Library/Bee/artifacts/WebGL/build/debug_WebGL_wasm/build.js failed with output:
Unknown option '--no-exit-runtime'

とのことで、--no-exit-runtime がどこかのタイミングで消えてしまったよう。

binaryen をビルドする… GitHub Actionsで!

仕方ないのでこれをビルドすることを試みてみることにしました。とはいっても手元に開発環境を用意するのはだるいので GitHub Actions で済ませました。

というわけで、このような変更を加えて:

https://github.com/rinsuki-lab/binaryen/compare/5387a0b1ffcaf925b40ee661063893c475d4c632...b17f9768382a38885b2476052205e5b722d5b5cb

できたものがこちらです (導入方法は GitHub 側読んでね & 自己責任で!)。

https://github.com/rinsuki-lab/binaryen/releases/tag/unity-2021.universal.version_101-30-g5387a0b1f.r1

導入することになるバージョンはこんな感じ:

$ ./wasm-opt --version
wasm-opt version 101 (version_101-35-gb17f97683)
$ file ./wasm-opt
./wasm-opt: Mach-O universal binary with 1 architecture: [arm64:Mach-O 64-bit executable arm64Mach-O 64-bit executable arm64]
./wasm-opt (for architecture arm64):    Mach-O 64-bit executable arm64

なんかテストがコケてるのが微妙ですが、まあ Unity のプロジェクトのビルドには問題なく使えたのでとりあえずこれでいいでしょう (リリース用のビルドする時には元に戻したほうがいいと思うけど)。

結果として、元々1分くらいかかっていたインクリメンタルビルドが45秒まで縮まりました (計測環境は M1 Max な MacBook Pro)。よかったですね!それでも若干遅いけど…。

(計測方法: 適当に Debug.Log のメッセージを書き換えて Command+B (≒ Build and Run) を押してからビルドが終わってブラウザが開くまでの時間をストップウォッチで測る)

ただ、まだ EmscriptenIntel 版な雰囲気があるので、Emscripten もちゃんと Apple Silicon 版に置換すればもうちょい早くなるかもしれないですね (そうこうしているうちに Unity の WebGL 版でやりたかったことはできないことが判明したので、また触る機会があったらそのうちやるかも…くらい)。ただ il2cpp とかが占めている割合も大きいのでどうかな…?