最速MVCフレームワークMithril.jsの速度の秘密

  • 696
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Mithril 0.2が本日リリースされました。ちょっとURLが変わったり( http://mithril.js.org/ )、API名が一部(m.moduleがm.mount)変わっていたり、コンポーネント機能がコーディング規約レベルから、専用のサポートAPIが追加されたりしていますが、0.1系と大した差はなさそうです。

某node.js会長とはいろいろ社内で話をしたりしたのですが、各種ベンチマークでもトップクラス、平均的には最速のクライアントサイドMVCフレームワークという称号を持ちながら、国内ではまだまだ知られていないMithril。レンダリング速度は仮想DOMの代名詞となったReact.jsの5倍以上(ベンチマークによります)です。

↓ホームページから転載

スクリーンショット 2015-05-02 0.00.03.png

ちなみにこちらのベンチマークで計測すると、MithrilはReact.jsの10倍以上速い結果になるのですが、これはちょっと計測方法に問題があって、実際はそこまで早くないので、こちらの結果は鵜呑みにしないように(pull-requestは一応出してますが)。

早さにはもちろん理由があるので、そこのところを紹介していきます。

ミクロな高速化

ミクロな高速化はReact.jsと同じ仮想DOMです。view関数が実行されて、そいつが返すJavaScriptのオブジェクトをキャッシュしておいて、差分検知して表示します。このあたりは他の仮想DOMともそうそう変わらないと思います。

ビューはオブジェクトでもクラスでもなんでもなくて、状態を持たない関数です。コントローラのオブジェクトを引数で受け取って、値へのアクセスなんかはそいつを経由したり、ビューモデルを経由したりします。ビューの中では、jQueryの $ 的なタグを作る m という関数を使って組み立てていきます。テンプレートといいつつ内部DSLですね。ビューのコンパイルもできて、それを行うと関数呼び出し形式を展開して高速化します(上記のグラフはこれを使わない結果)。React.jsも前者は当てはまりますが、後者の高速化はないです。

単なる関数ですが、config擬似属性というものを使って、レンダリング時に色々カスタマイズができます。これを使うと、既存のjQueryとかBootstrapでできたUI部品を簡単に表示できたりします。

function view(ctrl) {
    return m('html', [
        m('body', [
            m('h1', 'hello world')
        ])
    ]);
}

上記のコード片では使ってないですが、 m('div.menu') みたいに書くことも、 m('div', {className: 'menu'}) みたいに書くこともできます。JavaScriptのオブジェクト構文を使う場合は変更検知の対象になって、タグ名のところに書くと変更検知をスキップします。このようにして変更検知のコストを減らすことができます。

他のマニュアルの高速化として、差分検知の精度をあげるために、ID(key属性)をエレメントに渡して、移動とかも効率良く確実に検知できるようにしたり、プログラムで「ここから下は差分検知をしないでそのまま保持する」という指示ができるようになっていたりします。

ちなみに、 m() を手で書きまくらないと行けないかというとそんなことはなくて、公式のHTML→Mithrilテンプレート変換ツールにHTMLを渡すと上記の形式に変換できますし、React.jsのJSXツールをカスタマイズしてMithrilの内部DSL形式のテンプレートに変換するように改造されたMSXというものもあります。

マクロな高速化

MithrilはReact.jsと違って、完全なMVCフレームワークとして提供されています。システム全体が上記の仮想DOMエンジンの効率をさらに向上させるための仕組みになっています。

MVCなので、ビューがモデルを参照して、変更を検知ということは当然できます。この分野はECMA Script 6のObject.observe機能が待望されている領域なんですが、Mithrilはまったく違うアプローチを取っています。モデルのプロパティ(m.prop() で作る)では値の保存はできるんですが、変更を外部に通知する機能はないですし、ビューも変更をポーリングして探しに来るわけではありません。

じゃあなぜ変更を検知できるのか?というと、変更が発生するのは、ユーザの入力やAjaxなどの外部からのインタラクションで行われる、という前提を置いていて、イベントハンドラが完了したタイミングで再描画を仕掛けるという仕組みになっています。モデルを大量に書き換えてもそこからビュー側が呼ばれることはありません。例えるなら、格ゲーで画面内のキャラの動きを見て昇竜拳を出すのではなく、相手のスティックの出す音に反応するような、そんな感じです。

スクリーンショット 2015-05-02 0.49.29.png

また、内部カウンタを持っていて、イベントハンドラやAjax通信が発生すると、カウントが1上がります。終了すると1下がります。0になったときだけ再描画を行います。イベントハンドラからAjax通信を起動すると、通信が終わるまでは更新しません。また、 requestAnimationFrame() のコールバックを使用し、画面描画で有効なペース(1/60)以上にDOM操作をしないようになっています。このように、なるべく再描画自体の回数や頻度を最小にすることで高速化しています。

もちろん、必要であれば強制再描画をしかけることができます。外部の非同期通信ライブラリと連携したりするときはこのあたりの仕組みを知る必要があります。イベントは発生したんだけど、今回は値の更新はないよ、とわかっている時はアプリケーションコード側で「次の更新はスキップね」と指示を出したりできます。こちらもマニュアルでさらにチューニングできます。

GWのお勉強にMithril.jsはいかがですか?

0.2でAPIが一個増えたとはいえ、ECMAScriptのStringクラスのメソッドよりも少ない16個しか関数がなかったりします。それでいて、モデル・ビュー・コントローラを構成するのに必要な部品が提供されていて、ラウター(Router)機能もあってシングルページアプリケーションを作るのも簡単です。BootstrapやjQueryのUI部品とかと組み合わせることも難しくありません。その気になればIE6で使う方法もドキュメントに書かれているほど可搬性も高いですし、TypeScriptの型定義ファイルも同梱されています。

僕もまだ試してはないですが、Electronと組み合わせたり、ArangoDB上に乗っけてみたり、いろんなところで使えるはずです。Google Apps Script上に乗っけてサーバ費用ゼロのウェブアプリを作ってみてもいいと思います。エディタはVisual Studio Codeとか使ってみるとナウいんじゃないですかね?便利で機能の多いフレームワークもいいですが、小さくてコンパクトなやつも、勉強してツールボックスの中に入れておくと、簡単なことを実現したいときに役立つと思います。