GWTコンパイラのより良い使い方
こんにちは、ゼノフィkotsutsumiです。
多くの人が、構造化され、パワフルで、保全性があるWebアプリケーションをチームで作成するためにSencha GXTとGWTを利用しています。Dev ModeとSuper Dev Modeで簡単にJavaを記述でき、ブラウザーでの動作をテストする事ができます。そのあとにコンパイラでユーザーのブラウザーで動作する最適化されたコードを生成します。
実はコンパイラはカスタマイズ可能なので、あなたのニーズによって出力を変更するように指示でき、より良いビルドを生成できます。しかし、より良いビルドとは何でしょう?この定義はプロジェクトとチームメンバーによって変わります。多くの開発者にとっての、一番良いビルドはできる限り実行速度が速いこと、すぐテストができることでしょう。他のチームにとっての良いビルドはブラウザーがダウンロードするサイズが最も小さいもの、あるいはコンパイル後のトータルサイズが最小のものという場合もあるでしょう。
この記事でコンパイラが生成するものをカスタマイズするためのいくつかの方法と、それがビルド時間や出力サイズに与える影響について説明します。我々はSencha GXT Explorerを例として利用します。他のアプリケーションと比べてさほど複雑ではありませんが、Sencha GXTが提供するウィジェットのほぼ全てを利用しています。Sencha GWT 2.4.0とGXT 3.0.4を使うとExplorerは約183秒でビルドされ、最も大きいpermutation(コンパイルの結果出力されるファイル)は2.608.783バイトです。
Permutations
「普通」のビルドでは、ブラウザーのカテゴリーごとにに一つの、6つのpermutationが作成されます。 これらのカテゴリーは常に同一のものとして扱ってもよいぐらい関連性があるとしてGWTチームが選択したものです。このブラウザーはつぎのようになります。
- Firefox, 全てのバージョン
- 全てのWebkitブラウザー (Safari, Chrome, Android Browser, など)
- IE6 と IE7
- IE8
- IE9 (と IE10)
- Opera
GXTはデフォルトでこの6つのpermutationを利用するように設定されています、それは全ての可能性ごとに設定することと、全てを一緒くたにすることの間の丁度良い所をとっていると言えます。 GXTは普段リストされてないブラウザーをいくつか追加できます、IE6とIE7、IE9とIE10を分けられます。 ChromeとSafariを違うブラウザーとして(必要であれば異なっているバージョンも入れて)扱うこともできます。 Firefoxは二つのカテゴリにわけられます:Gecko 1.9の前と後です。そして特定の場合でテストが可能となります。 GXTはもう一つ調整できるところがあります。オペレーティングシステムです。各OSに特有の機能があるので、GXTがその違いを区別できるようにする事が重要です。
ビルド内にこのようなバリエーションがあるので、3.0.4では52ものpermutationが出力する事ができます! 普段の6つのpermutationよりビルド時間が非常に長くなりますが、それで得るものはありますか?
この質問をテストするためにアプリケーションのモジュールファイルにいくつか変更をしました。
1 | <inherits name="com.sencha.gxt.ui.GXT" /> |
GXTをロードする標準のinheritステートメントの代わりに、 継承する特定のモジュールを継承するステートメントに置き換えました。 これでウィジェットと状態管理コードとテーマだけをロードできます:
1 2 3 | <inherits name="com.sencha.gxt.widget.core.Core" /> <inherits name="com.sencha.gxt.state.State" /> <inherits name="com.sencha.gxt.theme.blue.Blue" /> |
現状Chartsはこの特定な変更を許可しないので、全体的にこの変更ができるためには、同じ変更をCharts.gwt.xmlに対しても施す必要がありました。
これはあまりになやり過ぎでさほど節約にもなりません。 最大のpermutationはこれで2.587.780バイトに下がりました、約21k(1%以下)の節約にしかなりませんが、ビルドにかかる時間は3分から20分に増えました。 面白いのは、26個のpermutationしか実際に生成されなかったことです。 OSチェックはWindows・Mac・Linux・Unknownの間の違いを区別できますが、実際にこのチェックの利点を得る必要は滅多にないので、ビルド時間の半分ぐらいが基本的に無駄になりました。 しかしまだ節約しているところがあります、 あるチームは対応するブラウザーに特化したビルドを生成し、デバッギングの時点で対応するブラウザーを制限することに価値を見いだすかもしれません。 そしてそのためにビルドに長い時間をかけてもいいと考えるかもしれません。
これはまた二つの違う方向にもって行けます、もっと少ないpermutationにブラウザーを合わせるか、または対応するブラウザーを制限することです。
まず対応するブラウザーを制限できます:
1 2 | <inherits name="com.sencha.gxt.ui.GXT" /> <set-property name="gxt.user.agent" value="ie9, ie10, gecko1_9, safari5, chrome, opera" /> |
GXTを再び継承して一番気に入っているブラウザーに限定して、ビルドされるpermutationを4つ — ie8、ie9/ie10、gecko1_9、safari5/chrome — にしています。 この6つのブラウザーをサポートするのに4つのpermutationだけでよいのは、似ているブラウザーを同じビルドに圧縮してるからです。 前のステップに従うと(GXTを直接継承しないで)これをまた6に戻すこともでき、そうすると最初よりもう少し特殊化できます。またはより小さいセットで良いこともあります。このもっと小さいセットでは約140秒でコンパイルできて、4つの中の最大のpermutationは2.608.785バイト、ほぼ最初とと同じです。
二つ目のオプションは対応するブラウザーをさらにより少ないpermutationに圧縮することです。これに対して二つのツールがあり、両方ともpermutationをマージするという方法です。最初は一つ以上のプロパティを折りたたむ(collapse)ように指定できます:
1 2 3 4 5 6 7 8 9 10 11 12 | <inherits name="com.sencha.gxt.widget.core.Core" /> <inherits name="com.sencha.gxt.state.State" /> <inherits name="com.sencha.gxt.theme.blue.Blue" /> <set-property name="gxt.user.agent" value="ie9, ie10, gecko1_9, safari5, chrome, opera" /> <!-- merge gecko1_9, safar5, and chrome into one permutation --> <collapse-property name="gxt.user.agent" values="gecko1_9, safari5, chrome" /> <!-- merge ie9 and ie10 into one permutation with a wildcard--> <collapse-property name="gxt.user.agent" values="ie*" /> <!-- merge all operating system permutations --> <collapse-property name="user.agent.os" values="*" /> |
これで4つのpermutationのセットが生成されます。 その最大のものは2.608.785バイトで144秒で完成します、まだベースラインより速く、対応するブラウザーが少ないのに、ほぼ同じサイズです。
最後にコンパイラに全てのプロパティが一つのpermutationに崩されるように指定されます:
1 | <collapse-all-properties /> |
これは大きい。 この行の前にどれだけのpermutationを設定したとしても、たった一つのファイルが生成されます。 これは特に速く作りたいビルドやビルドのサイズを小さくしたい(例えば6つの1.0MBファイルの代わりに一つの1.5MBのファイル)の場合にとても便利です。 Explorerの場合はコンパイルサイズが3,376,120バイトになりましたので、約700K以上大きくなっています。しかしコンパイルの時間は3分以上から1分を少し超えた程度に下がります。コンパイルされたディレクトリの全体サイズも22MBから6MB以下に減少します。
より詳しい情報がお望みなら、 プロパティ や permutations についてGWT wikiで詳しく知ることができます。
最適化レベルと他のフラグ
GWTのコンパイラがJavaをJavaScriptに書き直す時には、コードを多様に変換します。 いくつかの書き直しはJavaの式をJavaScriptに変換するためにいくつかの書き直しが必要です: instanceofオペレーターやキャストはメソッドコールに変更されますし、全てのクラスや継承はテーブル参照で追跡されます。 またコードを読むのは簡単で実行するのは難しいケースを探してそれを書き直しますし、デフォルトの値のままになっているフィールドは定数に変更され、時には他のものと一緒にコンパイルされます。 オプティマイザーの動作に対する変更はモジュールファイルではなくて、全てコンパイラを動作している時に設定されます。この変更をどう設定するかプロジェクトの作成方法によります:コマンドラインでAntを使う場合はコンパイラに与える引数で、Mavenを使う場合はコンフィグオプションで指定します。
高速なビルド
デフォルトでは、扱っているファイルに変化がなくともコンパイラは動作するようになっています。 多くのアプリケーションでは、ブラウザー特定の内容を開始する前にに8から15ステップの処理がオプティマイザーに通されます。 これも設定可能ですが、JavaをJavaScriptに正確に変更するためには最低でも一回は動作させないとなりません。 もし最適化のレベルを0に設定しますとできるだけ最適化を少なくするように指示しますし、9に設定しますと(これがデフォルト)これ以上できないところまで最適化するするようにコンパイラに指示します。
その他に「Aggressive Optimizations」と呼ばれている高い最適化のセットがあります。デフォルトでこれは有効使用可能とされていますが消すことはできます。
また「ドラフトモード」と呼ばれる便利なオプションがあります。 このオプションを有効にすると全てのアクティブな最適化を無効にして最適化のレベルも下げます。 これで最も簡単にコンパイラがアプリケーションを生成するために利用している努力を限定することができます。 ただドラフトモードをつけるだけでExplorerのビルド時間を180秒から103秒に削減しますが、最大のpermutationは3.426.639バイトと約800K大きくなります。 モジュール内のcollapse-all-permutationsディレクティブと組み合わせるとコンパイル時間はさらに49秒も短くなります。なんと1分以内です! 予想通りコンパイルサイズは増えて、4,459,653バイトになりまするので、これはローカルのデバッギングには問題ありませんがインターネット上でユーザーには提供したくないですね。
GWT Compiler:
コンパイラに-draftCompile flagを渡す
GWT-Maven-Plugin:
<draftCompile>true</draftCompile>
をプラグインのコンフィグにセットするか、gwt.draftCompileプロパティをtrueにする。
より小さなアプリ
その他にコードを単純にしてアプリケーションのコンパイルサイズを減らすフラッグもあります。このフラッグは全てのプロジェクトには合わないですが、考える価値はあると思います。 最初の段階はクラスのメタデータをスキップすることです。 これでClass.getName()が普通の”org.package.to.my.client.Object”の代わりに一般的な文字列を返すようになるので、出力にされる文字列の数を減らすことができます。 殆どのアプリケーションはその列が毎回同じになっているとは限らないので、利用方法を考える必要があります。
GWT Compiler:
コンパイラに-XdisableClassMetadataフラグを渡す
GWT-Maven-Plugin:
<disableClassMetaData>true</disableClassMetadata>
をプラグインのコンフィグにセットするか、
gwt.disableClassMetadataプロパティをtrueにする。
これはExplorerの場合で3.5%も節約でき、サイズが2,522,282バイトになります。 パッケージングとクラスを定義する文字列を削除しているだけなのでコンパイル時間はあまり変わりません。
二つ目のフラグはより侵略的で、全てのClassCastExceptionsをコンパイルコードから外します。 JavaはAタイプのオブジェクトをBタイプのように扱いたい時に指定するキャストオペレーターがありますが、JavaScriptはそのようなコンセプトはありませんので、全てのタイプ確認は人工的なコードで追加される必要があります。次のようなブロックで
1 2 3 4 | if (obj instanceof HasMethod) { HasMethod hasMethod = (HasMethod) obj; hasMethod.doSomething(); } |
はデフォルトでこのようなものにコンパイルされます:
1 2 3 4 | if (instanceOf(obj, 5)) { hasMethod = dynamicCast(obj, 5); hasMethod.doSomething(); } |
もしこの機能を有効にすれば、コンパイルされたJavaScriptはこのようになります:
1 2 3 4 | if (instanceOf(obj, 5)) { hasMethod = obj; hasMethod.doSomething(); } |
このフラグではアプリケーションの他の部分のキャストのオペレーションも削除します。 通常はこれは良いことです、「最も速いコードは実行しないコード」ですから、しかしコンパイルされたアプリケーションで予想外のClassCastExceptionsが外れることがあります。 全くエラーが現れないかもしれませんし、メソッドかフィールドが参照される時にJavaScript版のヌルポインターが発生するかもしれません。 もしプロダクション中でこのエラーが発生したら、Devモードかこのフラグセットを外すことで動作できますので正確なエラーを見ることができます。
GWT Compiler:
コンパイラに-XdisableCastCheckingフラグを渡す
GWT-Maven-Plugin:
<disableCastChecking>true</disableCastChecking>
をプラグインのコンフィグにセットするか、
gwt.disableCastCheckingプロパティをtrueにする。
これによってキャストの確認をしないので2%以下の節約ができますが、余分な確認をしないことで動作中のパフォーマンスにも影響を与えます。 クラスメタデータのように、クラス確認を外してもあまりコンパイル時間には影響はありません。
この機能を両方とも有効にするとコンパイルするには大体同じような時間がかかりますが、このもしかしたら役に立たない余分なデータと確認を外すことでコンパイルサイズが約6%減り、2,461,611バイトになります。
CssResourceのクラス名
次はClientBundleがCssResourcesを生成する方法の設定の変更があります。 デフォルトでClientBundleは一つ以上のアプリケーションがページ上で動作しているかもしれないと予期していて、生成されるCSSクラスについて注意しているので、衝突を起こすことはありません。 この機能のセットは様々な方法でカスタマイズできます。 それによりデバッギングがより簡単になったり、アプリケーションのサイズをより小さくできます。
GXT 3はCssResourcesとClientBundlesを非常に良く使います。 ウィジェットの描画と更新に使うイメージやスタイルを全てもっています。 アピアランスパターン により実際に利用されるCSSだけを含むようにできるため、より小さく最適化されたアプリケーションを作ることができます。
もしそのアプリケーション以外にはレンダリングされるコンテンツが無いことがわかっているなら、その余分な接頭を外せます。こうするとClientBundleが全てのCSSクラスをとても短い文字列(普段は3文字以下)にリネームできます。
この機能を使用可能とするにはこの行をモジュールファイルに追加して下さい。
1 | <set-configuration-property name="CssResource.obfuscationPrefix" value="empty" /> |
これは特にパワフルな最適化ではありませんが、全ては役に立ちます。 この変更はコンパイル時間には影響はありませんが、Explorerの場合サイズ約1k減らして、2,607,442バイトになります。
GWTとGXTを最新に保つ
最後にできることはコンパイラを最新版にすることです。 メインのGWTライブラリコードはブラウザーへの対応とバグの修正以外にはあまり変更しませんが、コンパイラ自体は徐々に変更、修正、改善が施されています。
UiBinderを利用している場合は、uibinder-bridgeをclasspathから外すこととinheritsをモジュールから外す事を忘れないで下さい。GWT 2.5.0以降を利用している時はもう必要ありません。
ただGWT 2.5.0に変換するだけでデフォルトのビルドが2,388,364バイトに下がりました(ほぼ10%、200Kの節約)、しかしコンパイル時間は少し増えました。
Closure コンパイラ
GWT 2.5.0のリリースでは、この処理に加えてコンパイルステップを追加することができます。 Closure コンパイラ(JavaScript最適化のコンパイラ)はより小さく効率的になる確かなルールを守るようにJavaScriptを書き直す事ができます。 この最適化はいくつかGWTコンパイラにも提供していますが、無いももあります。GWTの仕事が終わった後にClosure コンパイラが動作するようにするとコンパイルサイズかなり削減することができます。
既にGWTやJavaのコードはClosure コンパイラに対応していますが、JSNIのいくつかは対応していません。GXT 3.0.4のリリースの一部として、この最適化はGXTのアプリケーションを壊さないように保証するためにいくつか内部的な変更がなされました。
これはダウンロード時間が小さくなりますが、コストがかかります:アプリケーションの大きさによってコンパイル時間が急増する可能性があります。 この最適化は全てがコンパイルされた後にアプリケーションの各permutationに対して実行しなければなりませんので、マルチpermutationのアプリケーションに悪影響があるかもしれません。
GWT Compiler:
コンパイラに-XenableClosureCompilerフラグを渡す
GWT-Maven-Plugin:
<enableClosureCompiler>true</enableClosureCompiler>
をプラグインのコンフィグにセットするか、
gwt.compiler.enableClosureCompilerプロパティをtrueにする。
ただ2.5.0に変換するだけよりClosure コンパイラを有効にsたことで、さらに20Kを節約でき、全体のコンパイルサイズは2,388,364バイトになりましたが、ビルド時間は4分も余計にかかりました。 これはキャスト確認・クラスメタデータ・CSSクラス名の接頭などを無効にした合わせた節約よりは少ないですが、他方、それらの最適化にさらに積み重ねることもできます。 この全ての最適化を合わせるとコンパイルサイズを2MBを少し超える程度削減して、全体でみると300K、15%の節約になります。
Combining these optimizations
このオプションをベースにして、ベストの最適化の組み合わせは何でしょうか?
一つひとつのpermutationを最小化する(プロダクションにベスト):
- GWT 2.5.0に切り替える
- Closure コンパイラを使用可能にする
- クラスメタデータやクラスキャスト確認を無効にする
- CSSクラス名のプリフィックスを無効にする
コンパイルの高速化:
- 必要なブラウザーしか選ばない(オプショナル)
- 全てのpermutationを一つにまとめる
- Draftモードを有効にする
小さい出力全体のサイズを非常に小さくするが必要があれば(例えばエンベッデッドサーバー用にコンパイルするなどの場合)、組み合わせることができます — コンパイラにできるだけ作業をやらしてドラフトモードをオフにして、全てのプロパティをただ一つのpermutationにまとめるようにする。


凡例
- A — draft
- B — collapse-all
- C — disableClassMetaData
- D — disableClassChecking
- E — empty css classname prefix
- F — Closure Compiler (GWT 2.5 and above only)
このチャートを見るとコンパイルサイズと時間の間にほぼ逆数の関係ができていることを見てとることができます — コンパイラを長く動作させるほど、その分生産力が高まる。 最高峰ではコンパイラがExplorerを1分以内に終わらせる(2MBを少し超えた位で)。 しかしビルドの時間は何となく論理矛盾な事は見どころです: しかしビルドの時間は必ずしも一定でないことに注意してください — 同じ設定で作成しても同じ時間で完成するとは限りません。
まとめ
ビルドで何を得るのかは、もちろん入れるコードによりますが、それとともにコンパイラへ出力の生成方の指示にもよります。 必要なこと必要ではないことを指定すると、完了すべきタスクを選択でき、ビルドをできるだけ速く完成させることができます。
あなたはビルドに何が必要ですか?