汎用プラグインとしての WASM (WebAssembly) の可能性の模索 with LuaJIT 対決

昨日 WASM で色々実験していたのでその記録。もうちょっと型が強くなれば化けそう。
7
ロングもみあげガール推進部 @kb10uy

えーと、では昨日実験していた WASM の話をしようと思います(連ツイ/まとめ予定)

2020-11-14 13:02:15
ロングもみあげガール推進部 @kb10uy

そもそもどうしてこんなことをやろうと思ったかというと、割と前から ShortStoryServer の Playground では WASM が利用されていて、Web 以外でも用途が広がるんじゃないかと思ったからでして

2020-11-14 13:04:06
ロングもみあげガール推進部 @kb10uy

で、調べて初めて知ったんですけどそういう目的で使うために WASI (WebAssembly System Interface) ってのがあるんですね。まあ今回は WASI はあんまり関係なくて、どちらかというとそのランタイムであるところの wasmtime を見つけたことのほうがでかい

2020-11-14 13:05:44
ロングもみあげガール推進部 @kb10uy

wasmtime は Rust で実装されていて、しかもライブラリとして使えるときた。これは試さないわけにはいかないとなったわけですね(ここまで経緯)

2020-11-14 13:06:43

Wasmtime

A small and efficient runtime for WebAssembly & WASI

https://wasmtime.dev/

概要

ロングもみあげガール推進部 @kb10uy

ホストのプログラムに対して外部のコードを挿入して使う目的だと Lua とかが筆頭にあがるけど、今回はその用途に絞って比較してどんなもんのスピードが出るのかというのが実験内容

2020-11-14 13:08:15
ロングもみあげガール推進部 @kb10uy

試した処理の内容は 1. GCD (wasmtime のサンプルにあったので) 2. HDR 画像処理 (ちょうど卒論で使ってたので)

2020-11-14 13:15:24
ロングもみあげガール推進部 @kb10uy

注意: wasmtime は JIT が効くので、 Lua 側も mlua を使って LuaJIT バックエンドになるように調整済み

2020-11-14 13:16:55

実験1. GCD

ロングもみあげガール推進部 @kb10uy

まず GCD のほう。[1, 2^30] の乱数同士の GCD を求めるのを 100 万回やった結果が以下のツイートの通り(下段)。 ちなみにこれは Ryzen 5 3600 でやったけど Pentium G4560 でやったら "native" が 17ms になってた。Intel/AMD の違いなのか ABI の違いなのかは知らん twitter.com/kb10uy/status/…

2020-11-14 13:19:48
ロングもみあげガール推進部 @kb10uy

Lua と LuaJIT と WASM のマイクロベンチやってた pic.twitter.com/UeqQ9ySkwt

2020-11-13 16:37:47
拡大
ロングもみあげガール推進部 @kb10uy

LuaJIT に対して大体 3 倍ぐらい速かったけど、「いくらなんでもこれは LuaJIT が遅すぎるんじゃないか」と思い(下図参照)、別のタスクを用意した。 luajit.org/performance_x8… pic.twitter.com/SNh13p79Sk

2020-11-14 13:21:57
拡大

実験2. HDR 画像処理

ロングもみあげガール推進部 @kb10uy

で、本題の HDR 画像処理。内容としては「OpenEXR 画像に ACES Filmic Tonemapping Curve と ガンマ補正を適用する」というもの。ソース画像はフリーの 2048x1024 のこちらのものを使用 hdrihaven.com/hdri/?h=wide_s… pic.twitter.com/v00X7wAw8T

2020-11-14 13:25:24
拡大
ロングもみあげガール推進部 @kb10uy

まず結果から。同じようなコード、データを渡して処理させて受け取るまでのターンアラウンドを計測するとこんな感じになった: pic.twitter.com/U2GxxwJuB5

2020-11-14 13:30:56
拡大
ロングもみあげガール推進部 @kb10uy

WASM は native に対して x2~x3 程度の処理時間で収まっていて善戦しているのに対して LuaJIT が死ぬほど遅い。何ごとだよと。

2020-11-14 13:32:23
ロングもみあげガール推進部 @kb10uy

本当に関数を呼び出して返ってくる部分だけを計測するとこうなった。 LuaJIT が逆転した。 pic.twitter.com/vxC1oIudn1

2020-11-14 13:40:26
拡大
ロングもみあげガール推進部 @kb10uy

WASM 側もうちょっと速くなるんじゃないの?と思い、 WASM 側のコードにおけるガンマ補正を x.pow(1.0 / 2.2) から (x.log(E) * (1.0 / 2.2)).exp() に変更した。するとほぼ互角の速度になった pic.twitter.com/y8SaF8Dm0V

2020-11-14 13:43:34
拡大
ロングもみあげガール推進部 @kb10uy

副次的効果として WASM バイナリのサイズが 1.7KB ぐらい減った。 Rust の f32::powf (少なくとも wasm32-unknown-unknown) ではそこそこ重いっぽい

2020-11-14 13:45:08

考察(速度)

ロングもみあげガール推進部 @kb10uy

とりあえず速度的な観点としては、 WASM: 雑に書いてもまあまあ速い。ちゃんと最適化すれば相当速い。呼び出しバウンダリのコストは比較的低いっぽい LuaJIT: 普通に書いてかなり速い。ただデータによってはバウンダリのコストが死ぬほど高くつく

2020-11-14 13:48:01
ロングもみあげガール推進部 @kb10uy

言うの忘れてたけど 2048x1024 の R32G32B32 って 24MiB あるからね……

2020-11-14 13:49:38