先の投稿からずいぶん時間が経ってしまったが続編「その2」を書いてみる。
まず、問題となったものがどんなアプリかであるが、これは下図のように、スプレッドシートのような動作をするJavaScriptアプリで、サーバから必要なデータを初期表示の際にいっぺんに取ってきて、あとはブラウザ環境だけで動作するというものであった。タブを押すと別の画面が表示されるがサーバへのアクセスはない。表示されているタブの中に複数のテーブルがあり値を入力変更できる。しかし、値を変化させると隠れている他のタブの値まで影響するので、計算が多岐にわたって遅くなってしまう。これにはいくつか問題となるコーディングがなされていた。
問題となっていた部分を改善して効果があった順に挙げると以下となる。
1)イベントリスナーの多用をやめる
2)DOMへの直接参照をやめる
3)数値計算の誤差は最後にまるめる
1)イベントリスナーの多用をやめる
図を見てもわかるように、入力域は1テーブルで約50で、それが4~5あるのだから、合計約200~300の入力域になる。これら一つ一つにイベントを登録してしまうと、とっても遅くなる。これで悩んでいたころに、JUIというイベントがあって、JavaScriptいろいろ、個人的にOpenID そこでma.laさんが、DEMOを披露してくれて、DOM操作の高速化についていろいろ教えてくれた。懇談会ではイベントリスナーは1つというようなことをおっしゃっていたが、DEMOでは、on click で実装したおられたので真似してみた。これは非常に効果があった。
2)DOMへの直接参照をやめる
サーバサイドのオーソドックスな手法だと、テーブル内の入力域に表示される初期値はサーバ側でのHTML生成時にセットされることになる。初期表示だけであればいいのだが、今回のアプリのように、その値を基にして再計算するような場合には、すべての値に対してDOM参照が発生することになる。これがとても遅いのだ。そこで、思い切ってHTML動的生成はやめ、代わりにJSONを動的に出力する機能と、静的なHTMLを出力する機能に別けてもらうようにした。JSONを動的に出力する機能といっても別に難しいことを要求したわけではなく、HTML動的生成でHTMLタグを全部削除してJSONタグを書いてもらっただけである。また、静的なHTMLについてはWebサーバに置いてもらっただけである。
原理としては、一旦JSONをデータとして受け取り、表示の際にJavaScriptで静的HTMLの入力域に代入するということをしているが一瞬で書き換わるため以前のアプリと何ら変わらないように見える。以降、値に変更があった際には、JSONの値を元に再計算を行い、静的HTMLの入力域に代入すればよい。こうすることで、再計算中はDOMへの参照がなくなり、非常に高速になった。
とはいえ、計算後に全表示したのでは大きなテーブルでは遅くなってしまうので、DOM参照のためのリンクを持つようにした。(現時点では、この部分が一番遅くなってしまっている。何かいいアイデアはないものか・・)
ついでにいうと、このJSONオブジェクトのことを私たちはEntityと呼び、再計算のためのBlogicを呼び出すパラメータとして使用している。Blogicは、揮発性(ステートレス)であり、Entityを元に計算した結果のEntityを返すことしかしない。一方、Controlerへの呼び出しでもEntityをパラメータとして与えているが、これはサーバへの登録や参照機能を呼び出す際に使用している。(参考)3分で分かる設計の話
また、高速化とは関係ないが、Blogicをステートレスとすることで、分業という副産物を手に入れることができた。単体テストも機械的に行えることができ、JavaScriptを複数人でシステマチックに開発できるという事例にできた。
3)数値計算の誤差は最後にまるめる
JavaScriptの数値計算には誤差が含まれることをご存知だろうか。演算誤差とは
これを真に受けてしまうと演算を自前関数でやらなければならなくなる。実際に旧アプリではそうやっていたのだが当然ながら非常に遅くなってしまっていた。一つ一つの計算では誤差が発生するが、今回のアプリのように、実際に表示させる最後に丸めることでOKである場合がほとんどだろう。例えば、最終表示部分での丸めが小数点1桁で2桁目以降を四捨五入するとなっていた場合に、JavaScriptの演算誤差である小数点以下15桁が影響するには、どれだけの計算回数が必要かを検証してみればよいことである。影響ないことが検証できれば自前関数など破棄してNativeのまま使おう。
以上が主な高速化のポイントであったが、他にもカーソル移動時の高速化(参考)TABキーで移動後にFocus、Select などがある。
また、ワンソースマルチビューを実現する でも述べたが、以下のようにViewを表示するレイヤを機能で分けるという設計も非常に有効であった。例えば、Validatorで入力チェック、Attributeで表示桁や色、エラー処理などである。
1.Templateを選択する機能
- page id(xxxx.html#001)で指定
2.サーバからEntityを取得しBlogicを実行する
- Entityをチェックしてエラーコードをセットする。その後はAttributeによってエラーが表示される
- エラーがなければBlogicを実行する
3.静的なtemplateの上に動的なEntityをマッピングする。その際、Attributeにより値をフォーマットする(カンマを付けたり色を付けるなど)
4.データ入力時にvalidatorを実行する
5.(エラーが含まれていなければ)Entityをサーバに送信する
一つだけ注意すべきことがある。それは、Entityへのデータ入力点(この場合、サーバから取得した時点とキー入力の時点)におけるエラーチェックを厳密にすることだ。キー入力の時点では、Validatorとして古くからJavaScriptは一般的に使用されてきたが、サーバから読込んだ時点というのは意外な方も多いだろう。この設計では、ここをチェックしないとハングする場合もあるので要注意だ。
<関連>
高速化プロジェクト その1
0 件のコメント:
コメントを投稿