レベル: 中級 久世 宏明 (minamotonoason@gmail.com), Web プログラマー, (株)ネットエイト
2007年 12月 14日 パフォーマンス面を考慮したLZXコーディングの要点としては、各画面の表示(インスタンス生成)やデータ表示(データバインディング)という特に負荷の高い処理に対して注意を注ぐことになります。どちらの処理にしても、一度に全てを処理させず、いかに効果的に処理を後回しさせるかということに尽きます。本稿ではその点に重点を置いたLZXコーディングの方法を紹介します。
1. インスタンス生成プロセスの制御
Webアプリにはログイン画面、メニュー画面、登録画面、検索画面、データ表示画面、ヘルプ画面などなど、多くの画面が存在します。それぞれの画面の中にもボタンなどのたくさんの部品が配置されているでしょう。構築していくうちにアプリがどんどん巨大化していき、起動するだけでも大変時間がかかったり、あるいは遅すぎて起動エラーになったりしてしまうことがあります。これは、デフォルトでは起動時に全てのインスタンス生成プロセスが完了するのを待ってから画面表示を開始しようとするためです。
しかし、実際には例えばログイン時にはログイン画面さえ表示していれば良いでしょうし、ヘルプ画面やアラート画面などは毎回必ずしも必要ではないかもしれません。起動時に画面を用意する必要がない、つまりインスタンス生成する必要がないコンポーネントについては、個々のインスタンス生成のON/OFFや実行優先度を制御することで、起動や稼働中のパフォーマンスを向上させることが可能です。
ここではインスタンス生成の "保留" と "遅延" のコーディング方法について解説します。
1.1. インスタンス生成の保留(initstage="defer")
インスタンス生成の保留は、インスタンスが存在しない状態になるため、特に起動時間を短縮するには効果的です。
例えば、ログイン後にアプリコンテンツ画面が出てくる、といったよくあるパターンで説明します。
まず、調整前のリスト1のサンプルコードを実行すると図1のような画面が表示されます。
リスト 1. ログインシステムのサンプルコード
<?xml version="1.0" encoding="UTF-8"?>
<canvas debug="true">
<!-- 起動時間の計測 -->
<script>
var startTime = (new Date()).getTime();
</script>
<handler name="oninit">
var endTime = (new Date()).getTime();
if ($debug) Debug.write("canvas oninit: " + ( endTime - startTime ) + " ms");
</handler>
<!-- ログインパネル -->
<window x="50" y="100" width="250" height="180">
<constantlayout axis="x" value="10"/>
<constantlayout axis="y" value="10"/>
<view>
<text>Login:</text><edittext x="90"/>
<text y="40">Password:</text><edittext x="90" y="40"/>
<button align="center" y="90">Login</button>
</view>
</window>
<!-- アプリコンテンツ -->
<view name="contents">
<simplelayout axis="x"/>
<view bgcolor="aqua" width="200" height="300">
<simplelayout/>
<text>コンテンツ画面1</text>
<combobox/>
<combobox/>
<combobox/>
</view>
<view bgcolor="silver" width="200" height="300">
<simplelayout/>
<text>コンテンツ画面2</text>
<combobox/>
<combobox/>
<combobox/>
</view>
</view>
</canvas>
|
図 1. 全てのインスタンスが生成されてしまった例
起動時に全てのインスタンスが生成されているため、当然ながらログインパネルと同時にコンテンツ画面も表示されています。起動時間は筆者の環境で782msです。ここではログイン前にはコンテンツ画面は不要なので、コンテンツ画面のインスタンス生成を保留して非表示にしておくために、リスト2のように該当タグの属性として
initstage="defer"
を追加します。実行すると図2のようになります。
リスト 2. 表示しない画面のタグにinitstage="defer"を設定
<!-- アプリコンテンツ -->
<view name="contents" initstage="defer">
・
・
</view>
|
図 2. ログインパネルのみインスタンス生成
initstage="defer"を設定したコンテンツ画面<view>は図2のとおり画面に表示されなくなりました。インスタンス生成処理が保留された効果により起動時間が312msと半減しています。
initsgate="defer"なし・・・782ms
initsgate="defer"あり・・・312ms
保留中の処理を開始させるには、
completeInstantiation()メソッド
を実行します。initstage="defer"とcompleteInstantiationはセットで使用することになります。問題はどのタイミングで実行するかですが、ここではログインパネルのLoginボタンクリック時に実行することにします。リスト3のようにLoginボタンにonclickイベントハンドラを追加し、completeInstantiation()メソッドを記述します。ついでに、ログイン後に不要になるログインパネルをdestroy()して画面から消えるようにしておきます。
リスト 3. completeInstantiation()メソッド発行
<button align="center" y="90">Login
<handler name="onclick">
contents.completeInstantiation();
parent.parent.destroy();
</handler>
</button>
|
このように、起動時に不要な画面はdeferにして、必要時にcomleteInstantiation()を発行するようにすることで起動時間の短縮が図れます。
ちなみにこのサンプルと同じ動きは、コンテンツ<view>の visible="false" 設定でも可能です。ただし、この場合は「見えない」という見た目の挙動が同じなだけでインスタンス生成自体は実行されており、起動時間短縮には寄与していないので要注意です。
1.2. インスタンス生成の遅延(initstage="late")
initstage属性のdeferはインスタンス生成処理を保留、つまり完全に一時停止させますが、停止させないまでも "遅延" させる方法もパフォーマンス向上に有力です。initstage属性の値として
late
を使います。lateは、インスタンス生成処理の優先度を下げてアイドル時に実行されるという仕組みになっています。
リスト 4. コンテンツ<view>
<!-- コンテンツ1 -->
<view>
<simplelayout spacing="1"/>
<text>コンテンツ1</text> ・・・負荷試験のために20個ほど並べる
・
・
・
<text>コンテンツ1</text>
</view>
<!-- コンテンツ2 -->
<view x="100">
<simplelayout spacing="1"/>
<text>コンテンツ2</text> ・・・負荷試験のために20個ほど並べる
・
・
・
<text>コンテンツ2</text>
</view>
|
パフォーマンス調整前のリスト4のコードを実行すると全てが一度に表示されますが、筆者の環境で438msかかります。
ここで、リスト5のようにコンテンツ1とコンテンツ2の<view>に
initstage="late"
を設定すると、起動時間が172msになります。片方の<view>だけinitstage="late"にすると297msです。
initstage="late"なし・・・438ms
initstage="late"あり・・・172ms(両方の<view>に設定)、297ms(片方の<view>のみ設定)
リスト 5. initstage="late"の設定
<!-- コンテンツ1 -->
<view initstage="late">
~略~
<!-- コンテンツ2 -->
<view x="100" initstage="late">
|
真っ先に表示したい<view>のinitstageはデフォルト(=normal)にしておいて、少し遅れぎみで表示されても良いものはlateにするといいでしょう。コンポーネントやビューが膨大になり重くなったアプリケーションではlate設定をうまくちりばめると全体的になめらかに起動されてきます。
注意:initstage属性は、それが設定されたタグ自身ではなく子タグに対して作用するので、これまでのサンプルコードのように入れ子になっているタグの親タグに設定します。リスト6のようにinitstageを設定したタグに子タグがない場合は機能しないので要注意です。それでもinitstageを有効にしたい場合は、子タグとして<node/>、<view/>などをダミーで入れておけばOKです。
リスト 6. 子タグがないのでinitstageが機能しない例
<view width="100" height="100" bgcolor="red" initstage="defer" >
</view>
|
1.3. クラスの手動インスタンス化(new演算子)
new演算子
を使って標準クラスあるいは自作クラスのインスタンス生成を任意のタイミングで行うことができます。リスト1のアプリコンテンツ<view ・・・>をリスト7のように<class ・・・>に変更することで、コンテンツ画面<view>をクラス化します。
リスト 7. 自作クラス定義
<!-- アプリコンテンツ -->
<class name="contents" >
<simplelayout axis="x" />
・・・
</class>
|
クラスをインスタンス化するには、クラス名がタグ名になるのでこの例では<contents>タグを普通に配置するか、スクリプト内でnew演算子を使います。タグの配置では普通にインスタンス生成されてしまいますので、ここではnew演算子を使います。ボタンクリック時に、contentsクラスをcanvas直下にインスタンス生成するには、リスト8のように記述します。これで、initstage="defer"の時と同じく、任意のタイミングでインスタンス生成することができます。
リスト 8. canvas直下にcontentsクラスをインスタンス化
<button align="center" y="90">Login
<handler name="onclick">
new contents(canvas);
parent.parent.destroy();
</handler>
</button>
|
1.4. ステートの利用(<state>のapply()メソッド)
<state>を使ったインスタンス生成の制御も可能です。リスト9のようにアプリコンテンツ<view>を<state>~</state>で囲みます。<view>につけていたcontentsという名前を<state>につけなおし、<state>に対して制御を行うようにします。
<state>で囲んだ部分は、デフォルトではインスタンス生成しません。
<state>を使ったインスタンス生成制御は、クラス定義するほどでもない一部分に対して、例えば条件に応じた表示/非表示制御をする場合などに手軽です。
リスト 9. <state>でインスタンス生成制御したい範囲を囲む
<state name="contents">
<!-- アプリコンテンツ -->
<view>
・・・
</view>
</state>
|
<state>部分を有効化させるには、<state>の
apply()メソッド
を実行します。Loginボタンのonclickイベントハンドラにcontents.apply()をリスト10のように記述します。
リスト 10. apply()メソッドの実行
<button align="center" y="90">Login
<handler name="onclick">
contents.apply();
parent.parent.destroy();
</handler>
</button>
|
<state>~</state>部分をOFFにするには
remove()メソッド
を実行します。このときに自動的にdestroy()されますので、再度apply()した時には、リセットしたかのように再度インスタンス生成が実行されます。
インスタンス生成自体は負荷の高い処理です。2回目以降のapply()時のパフォーマンスを向上させるために、<state>の
pooling="true"
設定を使うことも考慮すべきでしょう。この設定を入れるとremove()時にdestroy()されず単に非表示になるだけなので、再apply()時のパフォーマンスが向上します。
またpooling="true"ではremove()前の状態を保持しているので、例えば検索結果を保持したまま画面をON/OFFしたい場合に便利な機能です。また、表示/非表示を繰り返すようなアラート画面のようなものにも適しているでしょう。同じ見た目の挙動をするvisible属性のtrue/false制御との違いは、アプリ起動時にインスタンス生成をしないことで起動時間短縮を図れる点です。
2. バインドデータの遅延複製
OpenLaszloの強力なデータ表示機能にデータバインディングがあります。コンポーネントにdatapath属性を設定することで、データセットからのデータをコンポーネントにバインドしてくれる機能で、データ数が複数でも存在する分だけコンポーネントのインスタンスを自動生成(複製)してくれる大変便利な機能です。
データバインディングのコーディングは簡単で、リスト11のようにXPath記法でdatapath設定を追加するだけです。
リスト 11. データバインディング
<text datapath="ds:/dummy/dum/@name" />
|
この便利な機能も、データ数が増えてくるにしたがって当然ながらパフォーマンスが落ちてきます。何万件ものデータを画面に表示するケースはあまりないと思いますが、大量のデータバインディングをさせようとすれば重すぎて図3のエラーが出て失敗してしまいます。
図 3. Flashのエラー
ここではデータバインディングの負荷を軽減し、パフォーマンスを向上させるための仕組みである
Lazyレプリケーション
の設定方法について解説します。Lazyレプリケーションとは全件のデータバインドを一気に行うのではなく、画面表示される件数分のみ順次処理を行うことでパフォーマンス向上を図る仕組みのことです。
2.1. サンプルデータ
本章で使うデータバインディング用のサンプルデータとして、リスト12のXMLファイルを用意します。データは1000件です。
リスト 12. XMLデータファイル(1000件)
<dummy>
<dum name="ダミー0001" />
・
・
・
<dum name="ダミー1000" />
</dummy>
|
2.2. Lazyレプリケーションその1(<datapath replication="lazy">)
Lazyレプリケーションの設定を<datapath>で行う例を紹介します。datapathはリスト11のコードでは<text>タグの属性になっていますが、レプリケーションの設定を行うためにリスト13のようにdatapathを<text>の子タグとして独立させます。そして<datapath>タグの属性として
replication="lazy"
を追加します。
リスト 13
<text>
<datapath xpath="ds:/dummy/dum/@name" replication="lazy" />
</text>
|
次のリスト14はサンプルXMLデータを読み込んで表示するサンプルコードです。筆者の環境での起動時間は次の通りで、その差は歴然です。
replication="lazy"なし・・・6906ms
replication="lazy"あり・・・ 312ms
リスト 14. データバインディングのサンプルコード(Lazyレプリケーション)
<canvas debug="true">
<!-- 起動時間の計測 -->
<script>
var startTime = (new Date()).getTime();
</script>
<handler name="oninit">
var endTime = (new Date()).getTime();
if ($debug) Debug.write("canvas oninit: " + ( endTime - startTime ) + " ms");
</handler>
<!-- データセット-->
<dataset name="ds" src="dummy1000.xml"/>
<!-- ビュー -->
<view width="100" height="100" clip="true">
<view>
<simplelayout/>
<text>
<datapath xpath="ds:/dummy/dum/@name" replication="lazy" />
</text>
</view>
<scrollbar/>
</view>
</canvas>
|
2.3. Lazyレプリケーションその2(dataoption="lazy")
リスト表示を行うコンポーネントの
dataoption属性
でもLazyレプリケーションの指定ができます。
リスト15は<list>内の<textlistitem>にサンプルXMLファイルからのデータをバインディングして表示するサンプルコードで、<list>の属性として
dataoption="lazy"
を設定しています。
リスト 15. <list>のLazyレプリケーション
<!-- リスト -->
<list shownitems="5" dataoption="lazy">
<textlistitem datapath="ds:/dummy/dum/@name"/>
</list>
|
リスト15の起動時間は筆者の環境で以下の通りです。
dataoption="lazy"なし・・・ 16547ms
dataoption="lazy"あり・・・ 312ms
dataoption="lazy"
はリスト16のように<combobox>にも使えます。
リスト 16. <combobox>のLazyレプリケーション
<!-- コンボボックス -->
<combobox shownitems="5" dataoption="lazy" editable="false">
<textlistitem datapath="ds:/dummy/dum/@name"/>
</combobox>
|
リスト16の起動時間は筆者の環境で以下の通りです。
dataoption="lazy"なし・・・27453ms
dataoption="lazy"あり・・・ 359ms
3. パフォーマンス向上TIPS
最後にOpenLaszloのwiki等に掲載されているパフォーマンスTIPSを紹介します。これまでのインスタンス生成やデータバインディングの調整に比べれば効果は微々たるもの(数十ms程度)ですが、それでも「塵も積もれば・・」ということで、少しでもパフォーマンス向上を目指すためのコーディング心得として受け止めていただければと思います
3.1. デバッグ行は if($debug) Debug.write() を使う
デバッグ行にはDebug.write()を記述していると思いますが、if($debug) Debug.write() にしておくと良いでしょう。
Debug.write()行の存在は、デバッグモードでなくても実行されており、若干ですが負荷がかかります。if($debug)をつけるとデバッグモードオフの時にコンパイラがその行を省くためパフォーマンスが向上します。しかも、ファイルサイズは大きくなりますが、アプリサイズは小さくなり有利です。
3.2. コンストレイントは可能な限り $once{ ・・・ }にする
他のコンポーネントの属性値を取り込むコンストレイント式( ${ ・・・ } )は大変便利ですが、内部的なイベント登録処理等のためにパフォーマンスに影響します。値の取得が動的に必要な場合は仕方ありませんが、起動時に一度だけ値を取得すれば済むような静的な使い方であれば、$once{ ・・・ } の使用をお勧めします。ケースによって効果は異なりますが、これによりパフォーマンス向上を期待できます。
さらに、あとでコードを見たときに、値の変更が発生しないコンストレイントであることを一目で判別できるという利点もあります。
3.3. ビューの数を最小限にする
OpenLaszloでいうビューとは<view>及びその継承クラスの総称で、ボタンやコンボボックス等を含め画面に表示されるもの全般を指します。
ビューのインスタンス生成負荷を最小限にするには、その数を可能な限り少なくする必要があります。特にレイアウト設計時に、不必要に<view>が入れ子になっているなど効率の悪い構造になっていないか要注意です。
3.4. 不要になったコンポーネントはdestroy()する
destroy()したからといってただちにメモリから消えるわけではありませんが、いつか来るかもしれないガーベッジコレクションに期待するのも価値はあるでしょう。
参考文献 学ぶために
製品や技術を入手するために
著者について  | |  | 京都市在住。長年サーバ、ネットワーク構築を経験してきましたが、多少のHTMLとスクリプトの知識で簡単にRIAを構築できるOpenLaszloに感心し、ひょんなきっかけで現在OpenLaszloベースのWebプログラマをしています。OpenLaszlo.jpの運営を担当しています。Laszlo Japanにはハンドルネームasonとして参加。音楽が趣味で、オフはデスメタルバンドのドラム担当。 |
記事の評価
|