ページ

2010年8月23日月曜日

[Silverlight] Silverlight アプリケーションの起動時に関するベストプラクティス

Silverlight Startup Best Practices」 より。
Silverlight アプリの起動を速くするために気をつけるべきことがまとめられていました。
なかなかおもしろかったのでざっと和訳してみました。

それにしても Info-ZIP で圧縮しなおしたらそれだけで 20% くらい小さくなるとか、ちょっと驚き。というか、起動時間を気にしなくちゃいけないくらいの規模のアプリにとっては 20% って大きいよな。
あと、紹介されている Tim Heuer 氏のビデオ 「Loading Dynamic XAPs and Assemblies」 ですが、これは Silverlight 2 のころの内容です。Silverlight 4 では MEF (Managed Extensibility Framework) を使えばいいんじゃないかと思います。

では、以下和訳。Introduction と Conclusion 以外はだいたい訳してますが、一部はしょったり適当だったりします。あと、そもそもそんなに英語力があるわけじゃないので間違ってるところもあるでしょう。なので、きちんとした内容は原文を見てください。

---- ここから和訳したもの

■ ダウンロードサイズを最小にしろ
起動する前にまずはダウンロードしなくちゃいけないわけだから、ダウンロードサイズは起動時間に直結する。複数の XAP ファイルに分けることができないか検討すべし。メイン画面に必要な部分と中核となる機能の部分だけを最初の XAP ファイルに入れておく。Tim Heuer 氏のビデオ 「Loading Dynamic XAPs and Assemblies」 にこういったやり方の詳細が解説されている。

他にもすばらしい方法がある。XAP ファイルを圧縮するんだ。Silverlight の XAP ファイルは ZIP ファイルをリネームしたものだけど、Silverlight 4 の XAP 圧縮アルゴリズムは最高のものってわけじゃない。最適な圧縮アルゴリズムの物を使って ZIP 圧縮しなおせばダウンロードサイズを 20% くらい (最高では 70% くらい) 小さくできるかも。詳細は David Anson 氏の 「Smaller is better!」 を参照。

■ ネットワーク I/O を待つな
これは起動中にしちゃうことの中でもっともリスキーなことのひとつ。ネットワーク I/O の待ち時間は一定ではない。Web サービスを呼び出したりネットワークからデータを取り出したりするとき、その要求は 2ミリ秒で済むかもしれないし、2秒かもしれないし、2分かもしれないし、それ以上かもしれない。UI を表示する前にデータが必要な場合は画面を表示しようがないかもしれない。一番いいのは、起動時間を決めるのがネットワーク接続の速さだってことに賭けちゃうってことかな。 せいぜい、アプリの起動時間を決定づけるネットワーク速度がそこそこ速いということに期待するぐらいしかなかろう [訳注: 原文は “At best, you are gambling on the speed of your network connection to determine your startup time.” こんな訳でいいのかまったく自信なし。いいとしたら、当然これは文章通りの意味ではなく、「だからそんなことにならないようにうまいこと考えろ」 ということだと思われ] [8/24 追記: コメントにてやねうらおさんに正しい訳を教えていただいたので修正。ただ、原著者さんは 「だからそんなアプリの作りにはするな」 ということが言いたいのであろうことは変わりません]

■ ディスク I/O を最小にしろ
ディスクからロードするデータが増えれば待ち時間も増える。

・起動時に読み込むアセンブリを少なくする
ディスク I/O を少なくするにはアプリのロード時に読み込まれるアセンブリを少なくすることだ。Silverlight では、CLR の just-in-time (JIT) コンパイラがあるメソッドに最初にアクセスしたときに、そのメソッドを含むアセンブリが読み込まれる。例: 単純な “Hello World” アプリに DataGrid を加える。すると DataGrid が含まれている System.Windows.Controls.Data.dll が起動時に読み込まれるようになる。Sysinternals の VMMap を使えば読み込んでいるアセンブリとそれぞれのメモリ使用量を見ることができる。[訳注: 原文には WMMap の使用例のスクリーンショットあり]

アセンブリはそれの型のどれかが使われるまで読み込まれないので、2つのやり方を覚えておくといい。

  1. スタティックメンバーはアセンブリの読み込みの依存関係に強く影響する。たとえば、DataGrid をスタティックフィールドに持つようなコードがあると、起動時にそのスタティックフィールドを初期化するために System.Windows.Controls.Data.dll が読み込まれる。これは System.Lazy<T> を使えば回避できる。詳細は、MSDN の 「Lazy の初期化」 などを。
  2. 必要になったときにアセンブリが読み込まれるようにメソッドを書き直す。
    [訳注: 以下、とりあえず原文のままコード例をコピペ。コンストラクタで生成するんじゃなく OnLoaded で生成するようにすれば起動時にアセンブリが読み込まれなくなる、ということだとはわかる。MehodImplOptions.NoInlining でインライン展開されないようにするってのはどういう意味なんだろ?インライン展開されても OnLoaded に組み込まれるだけだと思うんだけど。”if (b == true)” もわからないな。というかこれって最適化されたら if 文自体が消されちゃうと思うんだけど。]
private void OnLoaded(object sender, RoutedEventArgs e)
{
    bool b = true;
    if (b == true)
    {
        // Instead of this, refactor into a method, this will
        // prevent System.Windows.Controls.Data.dll where
        // DataGrid lives (and which is not one of the core
        // SL assemblies) from getting loaded.
        //   DataGrid myDataGrid = new DataGrid();
        //   LayoutRoot.Children.Add(myDataGrid);
        AddDataGrid();
    }
} 
//prevent in-lining if method implementation is too small.
[MethodImpl(MethodImplOptions.NoInlining)] 
public void AddDataGrid()
{
    DataGrid myDataGrid = new DataGrid();
    LayoutRoot.Children.Add(myDataGrid);
}

・データの読み込みを減らす
最初の表示に必要無いコンテンツやコンフィギュレーションデータの読み込みを避ける。例: フラットファイルにメッセージデータを保存している email クライアントがある。この場合、メイン画面が表示されてからメッセージデータを読み込むようにすべき。そして、ロード中アニメーションを表示するか、もしくは、操作可能であることを示すようなものを表示する。
他の例: 拡張可能なプラグインモデルのアプリケーション。この場合、メイン部分を表示してから各プラグインを読み込む。そうすれば無作法なプラグインによって起動に余計な時間がかかるのを防ぐことができる。

■ テンプレートとスタイルの最適化
アプリの初期表示が定義されているいくつかの XAML を起動時にパースする必要があるのは避けられない。だから、起動時間のために初期表示に使われる XAML を最適化すべき。

  • 要素数を最小に。ビジュアルツリー上の要素数がパースの時間に影響する。XAML をリファクタリングしたとき、無意味になった要素を見つけることがある。子がひとつしかなく、意味あるプロパティを持たないような Grid なんかがいい例だ。こういう要素はとっとと削除すべき。
  • 死んだ XAML を削除しろ。スタイルやツリーの一部に使っていないものがあったら削除するように!見えもしないし使うこともないものをなんで取っておく?
  • ユーザーコントロールにはテンプレートを使え。ユーザーコントロールはインスタンス化のたびにパースされるが、テンプレートは最初の一度だけパースされる。特に、同じスタイルのセルが何百もあるような DataGrid では DataGrid セル・テンプレートを使うことがとても重要。もしセルにユーザーコントロールが使われていたら何百回も同じ XAML をパースすることになるがテンプレートを使っていれば一回で済む。
  • プロパティにデフォルト値をセットするな。Opacity=”1” だとか、値なしの RenderTransform をセットするだとかいうような。デザインツールの中にはこういった嫌な癖を持つものもあるから出力結果を監視した方がいいかもしれない。我々もこの点がもっと良くなるように作業していく。

■ スプラッシュスクリーンの使うか検討する
起動時に関するベストプラクティスをいろいろ実装しても、まだ機敏さが足りないと思ったらどうする?そんなときはスプラッシュスクリーンを使うことを検討すべき。Silverlight アプリはデフォルトのスプラッシュスクリーンをもってる。(球体がくるくる回るやつ) このデフォルトのスプラッシュスクリーンを変更すれば起動時間が早くなったように見えるし、自分のブランドをすぐにユーザーに見せることができる。スプラッシュスクリーンを置き換える方法については 「方法 : Silverlight の単純なスプラッシュ スクリーンを定義する」 を参照。

4 件のコメント:

  1. > 一番いいのは、起動時間を決めるのがネットワーク接続の速さだってことに賭けちゃうってことかな。

    そこのat bestは「ベストにおいて」の意味ではなく、「せいぜい」の意味です。一種の諦めを含むベストなんでしょうね。あとそこのgambleは、「期待する」と訳すほうがいいでしょうね。それから、to determine~のto以下は直前の「the speed of connection」を修飾するのでその部分を修飾しているような訳にしないといけないです。

    訳例) せいぜい、アプリの起動時間を決定づけるネットワーク速度がそこそこ速いということに期待するぐらいしかなかろう。

    返信削除
  2. ↑少し言葉足らずだったので補足させてください。

    そこのyour startup timeは、起動中にネットワークI/Oを待っているようなアプリの起動時間のことです。この段落では、「そんなことはしちゃいけないよ」ということを力説してあります。

    「そんなことはしちゃいけないよ」と言ってるのに、もしそんなことをしてしまうなら、あなたに出来る最善な方法は「アプリの起動時間を決定づけるネットワーク速度がそこそこ速い」ことに賭ける(期待する)ぐらいのことしかやりようがありませんよという意味です。

    要するに、そんなことアプリの作りにするなということですね。

    返信削除
  3. やねうらお2010年8月23日 12:33

    ↑typoしました;; すみません。
    ×要するに、そんなことアプリの作りにするな
    ○要するに、そんなアプリの作りにするな

    返信削除
  4. なるほど、そのように読めばいいわけですね。
    とてもわかりやすい説明ありがとうございます。
    本文も訂正しました。

    返信削除