検証：MagJS の本当の描画パフォーマンス

# はじめに

[前回の記事](http://qiita.com/t-suwa/items/0ac5927af45b02289ba5)で、MagJS の計測結果についてはまだ納得できていないと書きました。

そこで検証方法を見直し、MagJS の本当の描画パフォーマンスについてほぼ納得のいく結果を得られたので、ここに記事として共有します。

# 前回の振り返り

MagJS は `requestAnimationFrame`（以降、`rAF` と略す）を使って描画処理を非同期化します。`rAF` に渡された関数が実行されるのは、ブラウザが次の再描画をする直前です。再描画のタイミングは 60fps を基準にディスプレイのリフレッシュレートと同期するように調整されます。

`rAF` を使うと描画処理がペンディングされ、すぐに制御が戻ってくるため、見かけ上のパフォーマンスはよく見えます。前回の計測結果はこの部分を見ていたので、桁違いのパフォーマンスとなっていました。しかし、そこには肝心の描画処理が含まれていません。

本当に知りたいのは、`rAF` に渡した関数の実行時間です。

どうやったらこれを計測することができるのか。そのヒントを得るために、再び [MagJS の描画ロジック](http://qiita.com/t-suwa/items/464d4fc57b0c5bec2f2c)を確認します。

# `makeRedrawFun` - 再描画関数を作成する関数

`rAF` に渡す描画関数は、`makeRedrawFun` で作ります。

```js:mag.js抜粋
  var makeRedrawFun = function(node1, idInstance1, force1) {
    return function(node, idInstance, force) {

      getNode(node.id, 1)

      // verify idInstance
      if (!isValidId(node.id, idInstance) || !node) {
        return
      }

      var state = mag.mod.getState(idInstance)

      // LIFE CYCLE EVENT
      if (mag.utils.callLCEvent('isupdate', state, node, idInstance)) return;

      var props = mag.mod.getProps(idInstance)

      var data = mag.utils.merge(mag.utils.copy(props), mag.utils.copy(state))

      // CACHED?
      if (mag.mod.iscached(idInstance, data) && !force) {
        return 0;
      };


      // LIFE CYCLE EVENT
      if (mag.utils.callLCEvent('willupdate', state, node, idInstance)) return;


      //RUN VIEW FUN
      mag.mod.callView(node, idInstance);

      //START DOM
      mag.fill.setId(node.id)
      mag.fill.run(node, state)
      // END DOM

      //CONFIGS
      callConfigs(node.id, mag.fill.configs)

      // add configs unloaders
      addConfigUnloaders(node.id, idInstance)

      // LIFE CYCLE EVENT
      mag.utils.callLCEvent('didupdate', state, node, idInstance)

      // get parent to call
      if(node && node.parentNode){
        var parent = findClosestId(node.parentNode)
        if(parent) {
          mag.redraw(parent, mag.utils.items.getItem(parent.id))
        }
      }
      
    }.bind({}, node1, idInstance1, force1)
  }
```

この流れを簡略化すると以下のようになります。

1. ライフサイクルイベントの `willupdate` を呼ぶ
2. `mag.mod.callView` で view 関数を呼ぶ
3. `mag.fill.run` で view 関数の結果を DOM に反映する
4. `_config` フックを呼ぶ
5. ライフサイクルイベントの `didupdate` を呼ぶ

というわけで、`willupadte` から `didupdate` までを計測すれば、描画処理のパフォーマンスがほぼわかると言って良さそうです。

素の JS や React のテストとは計測方法が異なってしまうものの、これ以上妥当なやり方はおそらくないので、これで進めることにします。

# MagJS の描画の最適化

MagJS は `Object.observe` でデータ変更を検出し、自動的に再描画を行います。このため、再描画処理中に `this` のプロパティに代入を行ったりすると、再帰的に再描画が走ります。

今回のテストでも不用意なコーディングで再描画が走っている個所があったので、これを見直しています。

# 計測結果

お待ちかねの計測結果はこちら。環境は前回と同じく、iMac late 2009 です。

![magjs-v0.21.3.png](https://qiita-image-store.s3.amazonaws.com/0/65212/755fac38-8ed5-9adb-4cb2-57d140dedc68.png)

驚くほど急激な右肩上がりのグラフです。これは、MagJS が DOM の差分検出していないことが多分に影響していると推測できます。率直に言って、かなり悪い数字です。

# 全体の比較

前回の計測結果と今回の結果をマージしたグラフです。

![totaltime.png](https://qiita-image-store.s3.amazonaws.com/0/65212/cf04caca-6ba6-11a1-b0f5-ad5592e39e0b.png)

これを見ると、もはや MagJS はお話にならないくらいパフォーマンスが悪いということがわかります。

# まとめ

MagJS のパフォーマンスを計測し直した結果、少なくとも現時点では、本番に耐えうるパフォーマンスではないということが明確になりました。残念ながら、MagJS は看板に偽りあり、と言わざるを得ません。

スーパークレイジーに速いと喧伝されていた各種パフォーマンステストの結果も、結局のところ、肝心の描画処理は省かれていた訳です。

自らの不明を恥じるとともに、今回の検証で少しでも間違った知見を訂正できればと思います。

MagJS 版の TodoMVC について解説記事を書く予定だったものの、こんな状況になってしまった以上もはやそこに意味はないため、次は Mithril のパフォーマンステストでもやってみようかと思っています。