WebAssembly触ってみた。 

Ruby3.2.0 Preview1 がリリースされました。
注目はWASIベースのWebAssemblyサポートではないでしょうか?

当記事では簡単なツールを作って、WebAssemblyがどのように活用できるか考えてみました。ツールに関してはソースを含めて動く状態のものをgithubにて公開しています。

目次

  1. WebAssemblyとは
  2. Wasmバイナリ
  3. メリット① 高速性
  4. メリット② 可搬性
  5. すばやく試す
  6. 自分のRubyアプリケーションをWASIパッケージ化する
  7. 簡単なアプリを作ってみた
  8. WebAssemblyをどう使うか?
  9. noteにおけるWebAssemblyの活用を考えてみる
  10. まとめ

WebAssemblyとは

簡潔にいうとサーバーサイド言語をブラウザで動くようにする機構といったところでしょうか。当記事においては「ブラウザでRubyが動く」ということになります。
正確な情報についてはウィキペディアやRubyのコミュニティWebサイトを参照していただきたい。

Wasmバイナリ

まず、Wasmバイナリについて説明する。WebAssemblyはRubyのソースコードをベースとして、Wasmバイナリを生成(コンパイル)する。生成されたWasmはバイナリ形式で定義されており、ネイティブコードなみに高速に実行される。Wasmは例えばRuby単体のruby.wasmとして生成されたものがあったり、自身の記述したコードを含むmyapp.wasmなどが生成されることもあるでしょう。
※Rubyであればruby.wasmとしてCDNで提供されており、ブラウザから直接読み込み実行することも可能

メリット① 高速性

前述の通り、Wasmはバイナリ形式で定義されており、ネイティブコードなみに高速に実行される。よって、Rubyのようなスクリプト言語でもコンパイル言語かつアセンブリのような低水準言語並のパフォーマンスで実行できるといえる。JavaScriptには荷が重くなってくる動画や画像系の処理などはユースケースとして、メインターゲットかなと思う。動作する環境はブラウザに限らないため、高速性の観点でいうならばRailsの中で動かすこともメリットはある。Rubyの環境でRubyが動かそうという不思議。

メリット② 可搬性

ruby.wasmというような1つのバイナリ形式のファイルで機能するため、完成物としての提供は非常にかんたん。更新する場合は単純に上書き。アプリケーション・サーバーが不要なのでWebサーバーさえあればWebサービスとしてある程度機能する。またindex.htmlと合わせてzipで固めてしまえばローカルツールとして機能させるユースケースもあるかもしれない。

すばやく試す

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.umd.js"></script>
  <title></title>
</head>
<body>
  <div class="container-sm p-5">
    <button type="button" class="btn btn-sm btn-primary my-3 px-3" onclick="main()">Run</button>
    <div id="result"></div>
  </div>
  <script>
    const { DefaultRubyVM } = window["ruby-wasm-wasi"];
    const main = async () => {
      const response = await fetch("https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm")
      const buffer = await response.arrayBuffer()
      const module = await WebAssembly.compile(buffer)
      const { vm } = await DefaultRubyVM(module)

      const res = vm.eval(`
        %w(C C++ Rust Ruby).sample
      `)

      const result = document.getElementById('result')
      result.innerText = res.toString()
    }
  </script>
</body>
</html>

copy

こちらはhtml単体で機能します。CDNから読み込んだRubyのWasmバイナリから、コードを実行しています。こちらのサンプルとほぼ同等です。

画像
Rubyが表示されました。

自分のRubyアプリケーションをWASIパッケージ化する

くわしくはこちら。詳しくは説明しませんが
wasi-vfsを使ってruby.wasmからmyapp.wasmを生成する感じでしょうか。

画像

サンプルを作ってみます。サイコロふるClassを一つ追加します。

$ wasi-vfs pack ruby.wasm --mapdir /src::./src --mapdir /usr::./head-wasm32-unknown-wasi-full/usr -o myapp.wasm

copy

# src/myapp.rb
class MyClass
  def self.dice
    rand(6) + 1
  end
end

copy

// javascript 抜粋
const res = vm.eval(`
  require '/src/myapp'
  MyClass.dice
`)

const result = document.getElementById('result')
result.innerText = res.toString()

copy

画像

Classが読み込めて実行できます。

こちら(ruby-head-wasm-wasi)のサンプルではRubyの中でjavascriptのコードをevalしていたりするので、インターフェースとしてもいくつか方式があるようです。(wasiはWebAssembly System Interface)

vm.eval(`
  require "js"
  luckiness = ["Lucky", "Unlucky"].sample
  JS::eval("document.body.innerText = '#{luckiness}'")
`);

copy

簡単なアプリを作ってみた

サンプルやチュートリアルばかりでもあれなので、一つ簡単なアプリケーション作ってみました。

画像

ダメボがついてて、戦闘ではなかなか活躍しそうで、頭も良いんですが、SAN値がかなりピンチですね。なにがあったんでしょう?

複雑な計算をWasmでやることにより画面処理を高速化しています(というほど複雑でもないんですが)。最近話題の「Stable Diffusion」など使ってキャラ絵の自動生成などまでできれば、WebAssemblyのユースケースとしてはかなりいい感じかもしれません。

大したものではないですがソースコード公開します。
https://github.com/semi6/ruby-wasm-kutulu

github-pagesで公開してみました。
https://semi6.github.io/ruby-wasm-kutulu/

そうwasmなら簡単に動くRubyのツールが公開できます。

さて、

WebAssemblyをどう使うか?

私もWebAssemblyは初心者なので、ここからは完全に想像でのお話です。

例えば、node.jsの初期、クライアントもサーバーもjavascriptで書ければいいよねという思想があったと思う。ちょっと当初の思想と異なる発展の仕方をしたような気はするが、現在では概ね達成できていると思う。WebAssemblyが同様のことができるかといえば、Noかなと思うけれど、ロジックの共通化という点は十分達成できるのでは無いかと思う。
(フロントからJavaScriptがなくなるとはたぶんみんな思ってない)

noteにおけるWebAssemblyの活用を考えてみる

noteの記事は現在htmlとして表示していますが、エディタやアプリなどではhtmlをパースして表現しなおしています。クライアントごとに個別に実装していますが、例えばJSONのような表現に直すことができればプラットフォームに依存しなくなります。短絡的に考えるとWebAssemblyであればサーバーで実装した上で、同様のコードをフロントで動かすことも可能です。そして、Rails側でもhashとして操作ができ、サーバーでNokogiriを振りかざす必要もなくなるということです。

画像

イメージするとこんな感じだろうか。
わりと現実的に見える。

もちろんRailsをまるっとパッケージングするわけにもいかないので、独立した処理機構として構成する必要はある。そして実際はS3やCDNを通して配布するべきだろう。通信もjsonになればネットワーク的なメリットもでてくる。

まとめ

Rubyは非常に開発体験が良い。コンパイルや、ビルドに依存しない、スクリプトのランタイムが実現するトライアンドエラーのサイクルは非常に生産性を高める。個人的にもRubyをキメて一番気持ちいいポイントはここだと思う。そしてパフォーマンス的な観点でいくらか、その代償を払っている。WebAssemblyによって、高速化、可搬性、生産性を兼ね備えたRubyが完成するというとさすが過言かと思うけれど、localでは.rbとして開発・動作して、staging, productionのような環境下で.wasmとして動作するとなると、非常に可能性を秘めている気にもなってくる。

おそらく今年のクリスマスにはRuby3.2がリリースされるでしょう。
楽しみに待ちたいですね。