パフォーマンスの最適化 : オブジェクトの動作
更新 : 2007 年 11 月
WPF オブジェクトに固有の動作を理解することは、機能とパフォーマンスのバランスを見極めるうえで役に立ちます。
このトピックには次のセクションが含まれています。
- オブジェクトのイベント ハンドラを削除しないとオブジェクトが維持される可能性がある
- 依存関係プロパティと依存関係オブジェクト
- Freezable オブジェクト
- ユーザー インターフェイスの仮想化
- 関連トピック
オブジェクトのイベント ハンドラを削除しないとオブジェクトが維持される可能性がある
オブジェクトがそのイベントに渡すデリゲートは、事実上そのオブジェクトへの参照です。このため、イベント ハンドラによってオブジェクトが本来より長く維持される可能性があります。オブジェクトのイベントをリッスンするように登録されているオブジェクトのクリーンアップを実行するときには、オブジェクトを解放する前にそのデリゲートを削除することが重要です。不要なオブジェクトが残っていると、アプリケーションのメモリ使用量が増加します。これは、オブジェクトが論理ツリーやビジュアル ツリーのルートである場合には特に重要になります。
WPF によって導入される弱いイベント リスナ パターンは、ソースとリスナのオブジェクト有効期間の関係の追跡が困難な場合に役に立ちます。一部の既存の WPF イベントはこのパターンを使用します。カスタム イベントを持つオブジェクトを実装する場合に、このパターンが役に立つこともあります。詳細については、「WeakEvent パターン」を参照してください。
CLR プロファイラや作業セット ビューアなど、特定のプロセスのメモリ使用量に関する情報を入手できるツールもいくつかあります。CLR プロファイラには、割り当てられた型のヒストグラム、割り当てグラフと呼び出しグラフ、さまざまなジェネレーションのガベージ コレクションとその結果のマネージ ヒープの状態を示す時系列、メソッドごとの割り当てとアセンブリの読み込みを示す呼び出しツリーなど、非常に便利な割り当てプロファイルのビューがいくつか含まれています。詳細については、.NET Framework Developer Center を参照してください。
作業セット ビューアは、特定のプロセスのメモリ使用量に関する情報を提供する WPF のパフォーマンス分析ツールです。このツールを使用すると、特定のアプリケーション状態におけるアプリケーションのメモリ使用量に関する情報のスナップショットを生成できます。WPF のパフォーマンス ツールの詳細については、「WPF のパフォーマンス プロファイリング ツール」を参照してください。
依存関係プロパティと依存関係オブジェクト
一般に、DependencyObject の依存関係プロパティへのアクセスが CLR プロパティへのアクセスに比べて遅いということはありません。プロパティ値の設定には多少のパフォーマンス オーバーヘッドがありますが、値の取得は、CLR プロパティから取得する場合と同じくらい高速です。この小さなパフォーマンス オーバーヘッドがある代わりに、依存関係プロパティでは、データ バインディング、アニメーション、継承、スタイル設定などの堅牢な機能がサポートされています。詳細については、「依存関係プロパティの概要」を参照してください。
DependencyProperty の最適化
アプリケーションで依存関係プロパティを定義するときには注意が必要です。定義する DependencyProperty が描画型のメタデータ オプションにしか影響しない場合 (AffectsMeasure などのその他のメタデータ オプションには影響しない場合) は、メタデータをオーバーライドしてそのようにマークする必要があります。プロパティ メタデータをオーバーライドまたは取得する方法の詳細については、「依存関係プロパティのメタデータ」を参照してください。
すべてのプロパティ変更が実際に測定、配置、および描画に影響するわけではない場合は、測定、配置、および描画の各パスを、プロパティ変更ハンドラによって手動で無効にした方が効率的な場合もあります。たとえば、設定した制限値より値が大きい場合にのみ背景を再描画する場合は、設定した制限値を値が超えていた場合にのみプロパティ変更ハンドラで描画を無効にします。
DependencyProperty を継承可能にするとパフォーマンスへの負荷が発生する
既定では、登録した依存関係プロパティは継承不可になります。ただし、プロパティはどれでも明示的に継承可能にすることができます。この機能は便利ですが、プロパティを継承可能にすると、プロパティの無効化の時間が増加して、パフォーマンスに影響します。
RegisterClassHandler は慎重に使用する
RegisterClassHandler を呼び出すと、インスタンスの状態を保存することができます。ただし、このハンドラはすべてのインスタンスで呼び出されるということを認識しておく必要があります。これはパフォーマンス上問題になる可能性があります。RegisterClassHandler を使用するのは、アプリケーションでインスタンスの状態を保存する必要がある場合だけにしてください。
DependencyProperty の既定値は登録時に設定する
既定値を必要とする DependencyProperty を作成するときには、DependencyProperty の Register メソッドにパラメータとして渡される既定のメタデータを使用してその値を設定します。コンストラクタや要素の各インスタンスでプロパティ値を設定するのではなく、この方法を使用するようにしてください。
Register を使用して PropertyMetadata の値を設定する
DependencyProperty を作成する場合、PropertyMetadata を設定するには、Register メソッドを使用する方法と OverrideMetadata メソッドを使用する方法があります。オブジェクトの静的コンストラクタで OverrideMetadata を呼び出すこともできますが、これは最適な解決方法とは言えず、パフォーマンスに影響します。最適なパフォーマンスを得るためには、Register の呼び出しの際に PropertyMetadata を設定します。
Freezable オブジェクト
Freezable は、固定されていない状態と固定された状態の 2 つの状態を持つ特殊な型のオブジェクトです。可能な場合は常にオブジェクトを固定するようにすると、アプリケーションのパフォーマンスが向上し、作業セットを縮小できます。詳細については、「Freezable オブジェクトの概要」を参照してください。
各 Freezable には、オブジェクトが変更されるたびに発生する Changed イベントがあります。ただし、変更通知はアプリケーションのパフォーマンスに影響を与えます。
たとえば、次の例では、各 Rectangle が同じ Brush オブジェクトを使用しています。
rectangle_1.Fill = myBrush;
rectangle_2.Fill = myBrush;
rectangle_3.Fill = myBrush;
// ...
rectangle_10.Fill = myBrush;
WPF には、SolidColorBrush オブジェクトの Changed イベントに対するイベント ハンドラが既定で用意されており、それを使用して Rectangle オブジェクトの Fill プロパティを無効にすることができます。この場合、SolidColorBrush では、Changed イベントを発生させる必要が生じるたびに、各 Rectangle のコールバック関数を呼び出す必要があります。このコールバック関数の呼び出しが積み重なると、パフォーマンスが大幅に低下します。さらに、この時点でハンドラを追加または削除すると、アプリケーションでリスト全体を検査しなければならなくなるため、パフォーマンスに大きく影響します。アプリケーションのシナリオで SolidColorBrush が変更されることがない場合は、Changed イベント ハンドラの維持に無駄な負荷を費やすことになります。
Freezable を固定すると、変更通知の維持にリソースを費やす必要がなくなるため、パフォーマンスが向上します。次の表は、単純な SolidColorBrush について、IsFrozen プロパティを true に設定した場合とそうでない場合のサイズの比較を示しています。ここでは、10 の Rectangle オブジェクトの Fill プロパティに 1 つのブラシを適用する場合を想定しています。
状態 |
サイズ |
---|---|
固定された SolidColorBrush |
212 バイト |
固定されていない SolidColorBrush |
972 バイト |
この概念の例を次のコード サンプルに示します。
Brush frozenBrush = new SolidColorBrush(Colors.Blue);
frozenBrush.Freeze();
Brush nonFrozenBrush = new SolidColorBrush(Colors.Blue);
for (int i = 0; i < 10; i++)
{
// Create a Rectangle using a non-frozed Brush.
Rectangle rectangleNonFrozen = new Rectangle();
rectangleNonFrozen.Fill = nonFrozenBrush;
// Create a Rectangle using a frozed Brush.
Rectangle rectangleFrozen = new Rectangle();
rectangleFrozen.Fill = frozenBrush;
}
固定されていない Freezable の Changed ハンドラによってオブジェクトが維持される可能性がある
Freezable オブジェクトの Changed イベントにオブジェクトが渡すデリゲートは、事実上そのオブジェクトへの参照です。このため、Changed イベント ハンドラによってオブジェクトが本来より長く維持される可能性があります。Freezable オブジェクトの Changed イベントをリッスンするように登録されているオブジェクトのクリーンアップを実行するときには、オブジェクトを解放する前にそのデリゲートを削除することが重要です。
WPF では、Changed イベントが内部でもフックされます。たとえば、Freezable を値として受け取るすべての依存関係プロパティは、自動的に Changed イベントをリッスンします。この概念の例として、Brush を受け取る Fill プロパティを次に示します。
Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;
myRectangle.Fill に myBrush を割り当てると、その Rectangle オブジェクトを指すデリゲートが SolidColorBrush オブジェクトの Changed イベントに追加されます。このため、次のコードを使用しても、myRect は実際にはガベージ コレクションの対象にはなりません。
myRectangle = null;
この場合、myBrush は myRectangle を引き続き維持し、それを Changed イベントを発生させるときにコールバックします。myBrush を新しい Rectangle の Fill プロパティに割り当てても、myBrush にイベント ハンドラがさらに追加されるだけです。
この種のオブジェクトをクリーンアップするための推奨される方法としては、Fill プロパティから Brush を削除します。これにより、Changed イベント ハンドラも削除されます。
myRectangle.Fill = null;
myRectangle = null;
ユーザー インターフェイスの仮想化
WPF には、データ バインド子コンテンツを自動的に "仮想化" する StackPanel 要素のバリエーションも用意されています。ここで、"仮想化" は、どの項目を画面に表示するかに基づいて、UIElements のサブセットを多数のデータ項目から生成する手法を指します。特定の時点ではごく一部の UI 要素しか表示されないにもかかわらず多数の UI 要素を生成すると、メモリとプロセッサの両方に負荷がかかります。VirtualizingStackPanel は、VirtualizingPanel で提供される機能を通じて、表示される項目を計算し、ItemsControl (ListBox や ListView など) の ItemContainerGenerator と共に機能することで、表示される項目に対応する UIElements だけを作成します。
パフォーマンスの最適化の一環として、これらの項目のビジュアル オブジェクトは、画面に表示される場合にのみ生成または維持されます。既にコントロールの表示可能領域にないビジュアル オブジェクトは削除される可能性があります。これをデータの仮想化と混同しないようにしてください。データの仮想化では、すべてのデータ オブジェクトがローカル コレクションに存在するのではなく、データ オブジェクトが必要に応じてストリームされます。
次の表は、5000 の TextBlock 要素を StackPanel と VirtualizingStackPanel に追加して描画した場合の経過時間を示しています。このシナリオの測定値は、テキスト文字列を ItemsControl オブジェクトの ItemsSource プロパティに割り当ててから、パネル要素にそのテキスト文字列が表示されるまでの時間を表します。
ホスト パネル |
レンダリング時間 (ミリ秒) |
---|---|
3210 |
|
46 |