(この記事はGrimoireJSアドベントカレンダー16日目の記事です。)
こんにちは。鼻血を流しながらGrimoireJS-Coreの最適化をしているmoajoです。
前回、最適化をやってるとか書きましたが、この作業が予想以上につらかったのでどんなことをしているのか衝動書きします。
#まずはプロファイリング
パフォーマンスを最適化するならまずはボトルネックを把握することからですね。
Chrome Developer Tool
のプロファイラを使いましょう。
僕はmac
使いなので、alt + command + i
でデベロッパーツールを起動して、Profiles
タブを選ぶだけです。
(スクショ撮り忘れてたので、最適化後の画像です。参考程度に。)
とりあえずCPU時間のプロファイルを取ってみるとこんな感じです。かなり便利。
ちなみに最初の時点で30fpsくらいしかでてません。重いです。
**GrimoireJS-Fundamental
**で普通のレンダリング処理をしているので、所要時間の大半は$renderViewPort
の呼び出しにかかってるのがわかります。この辺は5日目の記事で詳細が書いてました。
ほかにも、メモリ確保のプロファイリングもできるようです。素晴らしい。
#ゴリゴリ削る
まず一番重いのは、_broadCastMessage()でした。
まぁ、毎フレームシーン上のすべてのノードに$update
が送られるので、このあたりの最適化はかなり効果出そうですね。
メッセージレシーバのバインド
レシーバの中ではthis
がコンポーネントのインスタンスにバインドされます。
そのために呼び出しのたびに、bindしていましたが、結構重そうです。僕の環境では
コンポーネントのインスタンス生成時にバインドしておき、呼び出すだけに修正しました。
3fpsくらいあがりました。
ノード中のメッセージ送信先の探索
ノードはsendメッセージするときに、すべてのコンポーネントを線形探索して呼び出していましたが、
これも無駄なので、一度探索したら2回目以降は前回送信できたコンポーネントにのみ送信するよにしました。
(ちなみにこれに伴って、コンポーネントのインスタンスに動的にレシーバを追加したりしても呼ばれなくなる場合があります。その場合、そのノードの_messageCache={}
としてください。ただし非推奨です。)これも3fpsくらい。
##GomlNode#isActive
毎回呼ばれるのたびに先祖を辿って確認していました。これも無駄なのでキャッシュします。
早くはなったはずですがほぼ誤差でした。
##Dictionary
名前空間の解決に使う辞書の実装を、Mapクラスからただのオブジェクトと配列に変更しました。
結構早くなったはずです。(忘れたけど、3fpsくらい?)
##NSIdentity
名前空間を表すクラスですが、インスタンスがあまりにも大量に生成されてGCが走りまくっていたようです。
シングルトンっぽい実装にしたらインスタンスの数が100分の1くらいになりました。GCも軽減しました。
##SendMessage
メッセージを送るとき、先頭に$がついているか確認するのが重かったです。
あまりにも大量に呼ばれるとこんなところまで重いんですね。
すべてのノードで確認していたのを最初の一回だけに修正しました。
2fpsくらいあがりました。
##配列操作
mapとかreduceとかを多用していたのですが、普通に重いのと、新しい配列のインスタンスが作られるのでメモリ的にも負荷でした。アロー関数のインスタンス確保も重かったかもしれません。
for
で書き直してはやくなりました。もうfor
とか書きたくなかったんですけどね。辛いです。
#まとめ
なんだかんだ言って45fpsとかはでるようになりました。
これ以上のCoreでの最適化は僕の技量では厳しそうです。
ここからはfundamentalの方も最適化して行く予定です。
(というかこっちのほうが最適化の余地がある。)