WPF アプリケーションのパフォーマンスの最適化

このトピックは、Windows Presentation Foundation (WPF) アプリケーションのパフォーマンスを改善する方法を探している開発者にリファレンス情報を提供することを目的にしています。Microsoft .NET Framework Version 3.0 や WPF を初めて使用する開発者は、先にこの 2 つのプラットフォームについてよく学ぶ必要があります。このトピックは、両方のプラットフォームの実践的な知識があり、既に実行可能なアプリケーションを作成できるプログラマを対象としています。このトピックの情報はバージョン 1.0 の WPF に基づいています。

メモメモ :

このトピックで紹介するパフォーマンス データは、512 MB の RAM と ATI Radeon 9700 グラフィックス カードを搭載した 2.8 GHz の PC で WPF アプリケーションを実行した場合のデータです。

このトピックには次のセクションが含まれています。

  • パフォーマンスの計画
  • ハードウェアの利用
  • レイアウトとデザイン
  • 2D グラフィックスとイメージング
  • オブジェクトの動作
  • アプリケーション リソース
  • テキスト
  • データ連結
  • その他のパフォーマンスの推奨事項
  • WPF のパフォーマンス ツールとリソース
  • 関連トピック

パフォーマンスの計画

パフォーマンスの目標を達成できるかどうかは、適切なパフォーマンス計画を立てられるかどうかにかかっています。すべての製品の開発は計画から始まります。適切なパフォーマンス計画を立てるためのごく簡単なルールを以下にいくつか紹介します。

シナリオの観点から考える

シナリオを利用して、アプリケーションの重要なコンポーネントに的を絞ることができます。シナリオは、顧客や競合製品から導き出されるのが一般的です。常に顧客をよく観察し、顧客が自分たちの製品や競合他社の製品のどこに引き付けられているのかを解明します。顧客のフィードバックは、アプリケーションの主要なシナリオを特定するうえで役に立ちます。たとえば、起動時に使用されるコンポーネントを設計している場合は、そのコンポーネントが呼び出されるのは一般にアプリケーションの起動時の 1 回だけであるため、起動時間がキー シナリオになります。そのほか、一連のアニメーションの目的のフレーム レートや、アプリケーションの最大作業セットなどがキー シナリオになる場合もあります。

目標を定義する

目標は、アプリケーションのパフォーマンスが高いか低いかの判断に役立ちます。すべてのシナリオで目標を定義する必要があります。パフォーマンスの目標はすべて、顧客の期待に基づいて定義します。アプリケーション開発サイクルの早い段階では未解決の問題もまだ多く、パフォーマンスの目標を設定するのが困難な場合もあります。しかし、まったく目標を設定しないよりは、初期目標を設定してそれを後から修正する方が良いと言えます。

プラットフォームを理解する

アプリケーション開発サイクルでは、測定、調査、改良/修正というサイクルを常に維持します。開発サイクルの始めから終わりまで、信頼できる安定した環境でアプリケーションのパフォーマンスを測定する必要があります。外部要因による変動は避けるようにしてください。たとえば、パフォーマンスをテストするときには、ウイルス対策ソフトウェアや自動更新 (SMS など) を無効にして、それらがパフォーマンス テストの結果に影響しないようにする必要があります。アプリケーションのパフォーマンスの測定が完了したら、最大の効果を得るにはどこを変更すればよいのかを特定します。アプリケーションに変更を加えた後、再び同じサイクルを開始します。

パフォーマンス チューニングを反復プロセスにする

使用する各機能の相対負荷を知る必要があります。たとえば、Microsoft .NET Framework Version 3.0 のリフレクションを使用すると、一般にコンピューティング リソースの面でパフォーマンスへの影響が大きくなります。したがって、この機能は適切な判断に基づいて使用する必要があります。リフレクションを使用しないようにするということではありません。ただ、アプリケーションのパフォーマンスの要件と、使用する機能のパフォーマンスへの影響とのバランスに注意する必要があるということです。

豊かなグラフィックスを目指す

スケーラブルなアプローチで WPF アプリケーションのパフォーマンスの目標を達成するための主な手法の 1 つとして、豊かで複雑なグラフィックスを目指します。最初は常に、パフォーマンスへの影響が最も少ないリソースを使用してシナリオの目標を達成します。それらの目標を達成できたら、今度は、パフォーマンスへの影響が大きい機能を使用して豊かなグラフィックスを目指します。シナリオの目標は常に念頭に置いておきます。WPF はきわめて機能豊富なプラットフォームであり、多彩なグラフィックス機能が用意されています。パフォーマンスへの影響が大きい機能を何も考えずに使用すると、アプリケーション全体のパフォーマンスが低下する可能性があります。

WPF コントロールは本質的に拡張性が高く、コントロールの動作を変えずに外観を広範にカスタマイズすることができます。スタイル、データ テンプレート、およびコントロール テンプレートを活用することにより、パフォーマンスの要件に対応したカスタマイズ可能なユーザー インターフェイス (UI) を作成し、徐々に発展させていくことができます。「Photo Store のデモ」には、基本的なユーザー インターフェイス (UI) とアプリケーションのロジックをいかに簡単に分離できるかが示されています。この分離が確立されると、豊かなグラフィックスを目指すことができるようになります。

ハードウェアの利用

WPF の内部アーキテクチャには、ハードウェアとソフトウェアの 2 つのレンダリング パイプラインがあります。

ハードウェア レンダリング パイプライン

WPF のパフォーマンスを決定する最も重要な要因の 1 つは、それがレンダリング制約であるということです。つまり、描画するピクセルが増えるほどパフォーマンスへの負荷が大きくなります。ただし、グラフィック処理装置 (GPU) にオフロードできるレンダリングが増えれば、その分パフォーマンスが向上します。WPF アプリケーションのハードウェア レンダリング パイプラインは、Microsoft DirectX Version 7.0 以上をサポートするハードウェアの Microsoft DirectX 機能を最大限に活用します。Microsoft DirectX Version 7.0 と PixelShader 2.0+ の機能をサポートするハードウェアでは、さらなる最適化を実現できます。

ソフトウェア レンダリング パイプライン

WPF のソフトウェア レンダリング パイプラインは完全に CPU 制約です。WPF は、CPU の SSE/SSE2 命令セットを活用して、最適化されたフル機能のソフトウェア ラスタライザを実装します。ハードウェア レンダリング パイプラインを使用して実行できないアプリケーション機能のレンダリングは、シームレスにソフトウェア レンダリングに戻ります。

ソフトウェア モードでのレンダリングにおけるパフォーマンスの最大の問題は、塗りつぶし速度に関連する問題です。塗りつぶし速度は、描画するピクセルの数として定義されます。ソフトウェア レンダリング モードでのパフォーマンスに懸念がある場合は、ピクセルの再描画の回数をできるだけ減らすようにしてください。たとえば、アプリケーションに青い背景があり、その上にやや透明のイメージを描画する場合は、アプリケーションのすべてのピクセルが 2 回描画されることになります。その結果、アプリケーションにイメージが含まれている場合は、青い背景のみの場合に比べて描画に 2 倍の時間がかかることになります。

グラフィックスの描画層

アプリケーションが実行されるハードウェア構成を予測するのは非常に難しい場合があります。ただし、異なるハードウェアで実行された場合にシームレスに機能を切り替えられるようにアプリケーションを設計することもできます。これにより、アプリケーションでそれぞれのハードウェア構成を最大限に活用できます。

そのために、WPF にはシステムのグラフィックス機能を実行時に判別する機能が用意されています。グラフィックス機能の判別は、ビデオ カードを 3 つの描画層に分類することによって行われます。WPF が公開する API を使用して描画層を照会することにより、アプリケーションは、ハードウェアでサポートされている描画層に応じて、実行時に異なるコード パスを受け取ることができます。

描画層のレベルに大きく影響するグラフィックス ハードウェアの機能は、次のとおりです。

  • ビデオ RAM グラフィックス ハードウェアのビデオ メモリの量によって、グラフィックスを合成する際に使用できるバッファのサイズと数が決まります。

  • ピクセル シェーダ ピクセル シェーダは、ピクセル単位で効果を計算するグラフィックス処理関数です。表示するグラフィックスの解像度によっては、各表示フレームの処理に数百万ピクセルが必要な場合もあります。

  • 頂点シェーダ 頂点シェーダは、オブジェクトの頂点データの算術演算を実行するグラフィックス処理関数です。

  • マルチテクスチャのサポート マルチテクスチャがサポートされていると、3D グラフィックス オブジェクトのブレンド操作を行うときに、2 つ以上の別個のテクスチャを適用できます。マルチテクスチャのサポートの度合いは、グラフィックス ハードウェアのマルチテクスチャ ユニットの数によって決まります。

ピクセル シェーダ、頂点シェーダ、およびマルチテクスチャの各機能を使用して、DirectX の特定のバージョン レベルを定義し、次にこのバージョン レベルを使用して WPF のさまざまな描画層を定義します。

グラフィックス ハードウェアの機能によって、WPF アプリケーションの表示能力が決まります。WPF システムは、次の 3 つの描画階層を定義します。

  • 描画層 0 グラフィックス ハードウェアの加速が使用されません。DirectX のバージョン レベルは Version 7.0 未満です。

  • 描画層 1 グラフィックス ハードウェアの加速が部分的に使用されます。DirectX のバージョン レベルは、Version 7.0 以上で Version 9.0 未満です。

  • 描画層 2 ほとんどのグラフィックス機能でグラフィックス ハードウェアの加速を使用します。DirectX のバージョン レベルは Version 9.0 以上です。

WPF の描画層の詳細については、「グラフィックスの描画層」を参照してください。

レイアウトとデザイン

WPF アプリケーションの設計によっては、レイアウトの計算やオブジェクト参照の検証で不要なオーバーヘッドが発生して、パフォーマンスに影響が及ぶことがあります。詳細については、「レイアウト システム」を参照してください。

レイアウト

"レイアウト パス" という用語は、Panel 派生オブジェクトの子のコレクションのメンバを測定および配置して、それらを画面上に描画するプロセスを表します。レイアウト パスは数学的に増大するプロセスで、コレクション内の子の数が多くなれば、必要な計算の数も多くなります。たとえば、コレクション内の子 UIElement オブジェクトがその位置を変更するたびに、レイアウト システムによる新しいパスがトリガされる可能性があります。オブジェクトの特性とレイアウトの動作の間には密接な関係があるため、レイアウト システムを呼び出すことができるイベントの種類を把握することが重要です。レイアウト パスの不要な呼び出しをできるだけ減らすことで、アプリケーションのパフォーマンスを向上させることができます。

レイアウト システムは、コレクションの子メンバごとに、測定パスと配置パスという 2 つのパスを実行します。各子オブジェクトは、それぞれ固有のレイアウト動作を提供するために、Measure メソッドと Arrange メソッドの独自のオーバーライドされた実装を提供します。簡単に言うと、レイアウトは、要素のサイズ測定、配置、および画面上への描画を繰り返す再帰的なシステムです。

  • UIElement オブジェクトは、最初にそのコア プロパティを測定して、レイアウト プロセスを開始します。

  • オブジェクトのサイズに関連する FrameworkElement プロパティ (WidthHeightMargin など) が評価されます。

  • DockPanelDock プロパティや StackPanelOrientation プロパティなど、Panel 固有のロジックが適用されます。

  • すべての子オブジェクトが測定された後、コンテンツが配置されます。

  • 子オブジェクトのコレクションが画面に描画されます。

以下のアクションが発生すると、再びレイアウト パス プロセスが呼び出されます。

  • 子オブジェクトがコレクションに追加された場合。

  • 子オブジェクトに LayoutTransform が適用された場合。

  • 子オブジェクトに対して UpdateLayout メソッドが呼び出された場合。

  • 測定パスや配置パスに影響を与えるものとしてメタデータでマークされている依存関係プロパティの値が変更された場合。

可能な場合は最も効率的なパネルを使用する

使用する Panel 派生要素のレイアウト動作はレイアウト プロセスの複雑さに直接影響します。たとえば、Grid コントロールや StackPanel コントロールには Canvas コントロールよりはるかに多くの機能が用意されていますが、その代償として、パフォーマンスへの負荷も高くなります。Grid コントロールに用意されている機能が必要ない場合は、Canvas やカスタム パネルなど、パフォーマンスへの負荷が低いコントロールを代わりに使用するようにしてください。

詳細については、「パネルの概要」を参照してください。

RenderTransform は置き換えずに更新する

RenderTransform プロパティの値として、Transform を置き換えずに更新できる場合があります。アニメーションを含むシナリオでは特にこれが当てはまります。既存の Transform を更新すると、不要なレイアウト計算が開始されるのを防ぐことができます。

デザイン

オブジェクトの作成 (特に起動時の作成) はアプリケーションのパフォーマンス特性に影響する可能性があります。

ツリーはトップダウンで作成する

論理ツリーのノードが追加または削除されると、ノードの親とそのすべての子でプロパティの無効化が行われます。このため、常にトップダウンの作成パターンに従って、検証済みのノードで無駄に無効化が行われないようにする必要があります。ツリーをトップダウンで作成した場合とボトムアップで作成した場合の実行速度の違いを次の表に示します。このツリーには 150 のレベルがあり、各レベルに TextBlockDockPanel が 1 つずつ含まれています。

アクション ツリーの作成 (ミリ秒) レンダリング — ツリーの作成を含む (ミリ秒)

ボトムアップ

366

454

トップダウン

11

96

ツリーをトップダウンで作成する方法を次のコード例に示します。

private void OnBuildTreeTopDown(object sender, RoutedEventArgs e)
{
    TextBlock textBlock = new TextBlock();
    textBlock.Text = "Default";

    DockPanel parentPanel = new DockPanel();
    DockPanel childPanel;

    myCanvas.Children.Add(parentPanel);
    myCanvas.Children.Add(textBlock);

    for (int i = 0; i < 150; i++)
    {
        textBlock = new TextBlock();
        textBlock.Text = "Default";
        parentPanel.Children.Add(textBlock);

        childPanel = new DockPanel();
        parentPanel.Children.Add(childPanel);
        parentPanel = childPanel;
    }
}

論理ツリーの詳細については、「要素ツリー」を参照してください。

2D グラフィックスとイメージング

WPF には、アプリケーションの要件に合わせて最適化できる広範な 2D グラフィックス機能とイメージング機能が用意されています。

描画と図形

WPF には、グラフィカルな描画コンテンツを表現するために DrawingShape の両方のオブジェクトが用意されています。ただし、Drawing オブジェクトの方が Shape オブジェクトより単純で、パフォーマンス特性に優れています。

Shape を使用すると、画面にグラフィカルな図形を描画できます。Shape オブジェクトは、FrameworkElement クラスから派生するため、パネルおよびほとんどのコントロール内で使用できます。

WPF には、グラフィックス サービスやレンダリング サービスへのアクセスのレイヤがいくつか用意されています。Shape オブジェクトは最上位レイヤで使いやすく、レイアウトやイベント処理などのさまざまな役立つ機能を提供します。WPF には、すぐに使用できるさまざまな図形オブジェクトが用意されています。すべての図形オブジェクトは Shape クラスから継承されます。使用可能な図形オブジェクトには、EllipseLinePathPolygonPolylineRectangle などがあります。

一方、Drawing オブジェクトは FrameworkElement クラスから派生していません。Drawing オブジェクトは、図形、イメージ、およびテキストを描画するためのより軽量な実装を提供します。

Drawing オブジェクトには次の 4 つの種類があります。

  • GeometryDrawing – 図形を描画します。

  • ImageDrawing – イメージを描画します。

  • GlyphRunDrawing – テキストを描画します。

  • DrawingGroup – その他の描画を描画します。他の描画を 1 つの複合描画に結合するには、描画グループを使用します。

GeometryDrawing オブジェクトは、ジオメトリ コンテンツを描画するために使用されます。Geometry クラスと、そこから派生する具象クラス (CombinedGeometryEllipseGeometryPathGeometry など) は、2D グラフィックスを描画するための手段を提供します。ヒット テストやクリッピングもサポートされています。ジオメトリ オブジェクトを使用すると、たとえば、コントロールの領域を定義したり、イメージに適用するクリップ領域を定義したりすることができます。ジオメトリ オブジェクトは、四角形や円などの単純な領域にすることも、2 つ以上のジオメトリ オブジェクトから作成された複合的な領域にすることもできます。さらに複雑な幾何学領域を作成するには、ArcSegmentBezierSegmentQuadraticBezierSegment などの PathSegment 派生オブジェクトの組み合わせを使用します。

表面的には、Geometry クラスと Shape クラスはよく似ています。いずれのクラスも 2D グラフィックスの描画に使用され、それぞれのクラスから派生する具象クラスも似ています (EllipseGeometryEllipse など)。ただし、この 2 つのクラスのセットの間には重要な違いがあります。その 1 つとして、Geometry クラスには、Shape クラスの一部の機能 (図形そのものを描画する機能など) がありません。ジオメトリ オブジェクトを描画するには、DrawingContext、Drawing、Path (Path が Shape であることは注目に値します) などの別のクラスを使用して描画操作を実行する必要があります。塗りつぶし、ストローク、ストロークの太さなどの描画プロパティは、ジオメトリ オブジェクトを描画するクラスにあります。一方、図形オブジェクトにはこれらのプロパティが含まれています。この違いは、ジオメトリ オブジェクトが円などの領域を定義するのに対し、図形オブジェクトは領域を定義し、領域の塗りつぶしやアウトラインを定義し、レイアウト システムに参加する、と考えることができます。

Shape オブジェクトは FrameworkElement クラスから派生するため、Shape オブジェクトを使用するとアプリケーションのメモリ消費が大幅に増加する可能性があります。使用するグラフィカル コンテンツで FrameworkElement の機能がまったく必要ない場合は、より軽量な Drawing オブジェクトを使用することを検討してください。

Drawing オブジェクトの詳細については、「Drawing オブジェクトの概要」を参照してください。

StreamGeometry オブジェクト

StreamGeometry オブジェクトは、幾何学図形を作成するための PathGeometry の代替となる軽量なものです。StreamGeometry は、複雑なジオメトリを作成する必要がある場合に使用します。多くの PathGeometry オブジェクトの処理に最適化されているため、多数の PathGeometry オブジェクトを個別に使用する場合に比べてパフォーマンスが向上します。

次の例では、XAML で属性構文を使用して三角形の StreamGeometry を作成します。

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel>
  
    <Path Data="F0 M10,100 L100,100 100,50Z" 
      StrokeThickness="1" Stroke="Black"/>

  </StackPanel>
</Page>

StreamGeometry オブジェクトの詳細については、「方法 : StreamGeometry を使用して図形を作成する」を参照してください。

DrawingVisual オブジェクト

DrawingVisual オブジェクトは、図形、イメージ、またはテキストの描画に使用する軽量の描画クラスです。このクラスが軽量と見なされる理由は、レイアウトやイベントの処理を実現しないため、パフォーマンスが向上するからです。このため、描画は背景やクリップ アートに適しています。詳細については、「DrawingVisual オブジェクトの使用」を参照してください。

イメージ

WPF のイメージング機能は、以前のバージョンの Windows のイメージング機能から大幅に強化されています。ビットマップの表示、または一般的なコントロール上でのイメージの使用などのイメージング機能は、以前は主に Microsoft Windows Graphics Device Interface (GDI) または Microsoft Windows GDI+ のアプリケーション プログラミング インターフェイス (API) によって処理されていました。これらの API では、基本的なイメージング機能は提供されていましたが、コーデック拡張機能のサポートや忠実性の高いイメージのサポートなどの機能は含まれていませんでした。WPF Imaging API は、GDI および GDI+ の欠点を克服し、アプリケーション内でイメージを表示および使用するための新しい API のセットを提供するために再設計されています。

イメージを使用する際には、パフォーマンスを向上させるために以下の推奨事項を考慮してください。

  • アプリケーションでサムネイル イメージを表示する必要がある場合は、縮小版のイメージを作成することを検討してください。既定では、WPF は読み込んだイメージを本来のサイズにデコードします。サムネイル バージョンのイメージのみが必要な場合は、イメージを本来のサイズにデコードしてからサムネイル サイズに縮小するという無駄が生じます。この不要なオーバーヘッドを回避するには、WPF に対してイメージをサムネイル サイズにデコードするように要求するか、サムネイル サイズのイメージを読み込むように要求します。

  • イメージは常に、既定のサイズではなく必要なサイズにデコードするようにしてください。上で説明したように、既定の実際のサイズではなく必要なサイズにイメージをデコードするように WPF に要求します。これにより、アプリケーションの作業セットを縮小できるだけでなく、実行速度も向上します。

  • 可能であれば、複数のイメージを 1 つに結合します (複数のイメージから成るフィルム ストリップなど)。

  • 詳細については、「イメージングの概要」を参照してください。

BitmapScalingMode

ビットマップのスケーリングをアニメーション化する場合、既定の高品質イメージの再サンプリング アルゴリズムは、フレーム レートを低下させるほどシステム リソースを消費する場合があり、実際にはアニメーションの動きが滑らかでなくなることがあります。RenderOptions オブジェクトの BitmapScalingMode プロパティを LowQuality に設定すると、ビットマップのスケーリングのアニメーションがより滑らかになります。LowQuality モードは、WPF のレンダリング エンジンに対して、イメージを処理するときに品質重視のアルゴリズムから速度重視のアルゴリズムに切り替えるように指示します。

イメージ オブジェクトの BitmapScalingMode を設定する方法を次の例に示します。

// Set the bitmap scaling mode for the image to render faster.
RenderOptions.SetBitmapScalingMode(MyImage, BitmapScalingMode.LowQuality);

CachingHint

既定では、WPF は、TileBrush オブジェクト (DrawingBrushVisualBrush など) の描画された内容をキャッシュしません。シーンで TileBrush の内容も使用状況も変化することのない静的なシナリオでは、ビデオ メモリが節約されるため、この動作には意味があります。静的な内容の TileBrush を静的でない方法で使用する場合には、あまり意味はありません。たとえば、静的な DrawingBrushVisualBrush が回転する 3D オブジェクトのサーフェイスにマップされている場合などです。WPF の既定の動作では、内容が変化しない場合でも、DrawingBrush または VisualBrush のすべてのフレームの内容全体が再描画されます。

RenderOptions オブジェクトの CachingHint プロパティを Cache に設定すると、並べて表示されたブラシ オブジェクトのキャッシュ バージョンを使用してパフォーマンスを向上させることができます。

CacheInvalidationThresholdMinimum および CacheInvalidationThresholdMaximum の各プロパティ値は、スケールの変化に伴って TileBrush オブジェクトをいつ再生成する必要があるかを決定する相対的なサイズ値です。たとえば、CacheInvalidationThresholdMaximum プロパティを 2.0 に設定すると、TileBrush のキャッシュ サイズが現在のキャッシュ サイズの倍を超えた場合にだけ、キャッシュを再生成すれば済みます。

DrawingBrush のキャッシュ ヒント オプションの使用方法を次の例に示します。

// Set the minimum and maximum relative sizes for regenerating the tiled brush.
RenderOptions.SetCacheInvalidationThresholdMinimum(drawingBrush, 0.5);
RenderOptions.SetCacheInvalidationThresholdMaximum(drawingBrush, 2.0);

// The tiled brush will be regenerated when the size is
//   0.5x, 0.25x (and so forth)
// and
//   2x, 4x, 8x (and so forth)
// of the original size.

// Set the caching hint option for the brush.
RenderOptions.SetCachingHint(drawingBrush, CachingHint.Cache);

オブジェクトの動作

WPF オブジェクトに固有の動作を理解することは、機能とパフォーマンスのバランスを見極めるうえで役に立ちます。

オブジェクトのイベント ハンドラを削除しないとオブジェクトが維持される可能性がある

オブジェクトがそのイベントに渡すデリゲートは、事実上そのオブジェクトへの参照です。このため、イベント ハンドラによってオブジェクトが本来より長く維持される可能性があります。オブジェクトのイベントをリッスンするように登録されているオブジェクトのクリーンアップを実行するときには、オブジェクトを解放する前にそのデリゲートを削除することが重要です。不要なオブジェクトが残っていると、アプリケーションのメモリ使用量が増加します。このことは、そのオブジェクトが論理ツリーやビジュアル ツリーのルートである場合には特に重要になります。

WPF によって導入される弱いイベント リスナ パターンは、ソースとリスナのオブジェクト有効期間の関係の追跡が困難な場合に役に立ちます。一部の既存の WPF イベントはこのパターンを使用します。カスタム イベントを持つオブジェクトを実装する場合にこのパターンが役に立つこともあります。詳細については、「WeakEvent パターン」を参照してください。

CLR プロファイラや作業セット ビューアなど、特定のプロセスのメモリ使用量に関する情報を入手できるツールもいくつかあります。CLR プロファイラには、割り当てられた型のヒストグラム、割り当てグラフと呼び出しグラフ、さまざまなジェネレーションのガベージ コレクションとその結果のマネージ ヒープの状態を示す時系列、メソッドごとの割り当てとアセンブリの読み込みを示す呼び出しツリーなど、非常に便利な割り当てプロファイルのビューがいくつか含まれています。詳細については、Microsoft .NET Framework Developer Center を参照してください。

作業セット ビューアは、特定のプロセスのメモリ使用量に関する情報を提供する WPF のパフォーマンス分析ツールです。このツールを使用すると、特定のアプリケーション状態におけるアプリケーションのメモリ使用量に関する情報のスナップショットを生成できます。WPF パフォーマンス ツールの詳細については、「WPF のパフォーマンス プロファイリング ツール」を参照してください。

依存関係プロパティと依存関係オブジェクト

DependencyObject の依存関係プロパティへのアクセスが CLR プロパティへのアクセスに比べて遅いということは一般にありません。プロパティ値の設定には多少のパフォーマンス オーバーヘッドがありますが、値の取得は、CLR プロパティから取得する場合と同じくらい高速です。この小さなパフォーマンス オーバーヘッドがある代わりに、依存関係プロパティでは、データ バインディング、アニメーション、継承、スタイル設定などの堅牢な機能がサポートされています。詳細については、「依存関係プロパティの概要」を参照してください。

DependencyProperty の最適化

アプリケーションで依存関係プロパティを定義するときには注意が必要です。定義する DependencyProperty が描画型のメタデータ オプションにしか影響しない場合 (AffectsMeasure などのその他のメタデータ オプションには影響しない場合) は、メタデータをオーバーライドしてそのようにマークする必要があります。プロパティ メタデータをオーバーライドまたは取得する方法の詳細については、「依存関係プロパティのメタデータ」を参照してください。

すべてのプロパティ変更が実際に測定、配置、および描画に影響するわけではない場合は、測定、配置、および描画の各パスをプロパティ変更ハンドラで手動で無効にした方が効率的な場合もあります。たとえば、設定した制限値より値が大きい場合にのみ背景を再描画する場合は、設定した制限値を値が超えていた場合にのみプロパティ変更ハンドラで描画を無効にします。

DependencyProperty を継承可能にするとパフォーマンスへの負荷が発生する

既定では、登録した依存関係プロパティは継承不可になります。ただし、プロパティはどれでも明示的に継承可能にすることができます。この機能は便利ですが、プロパティを継承可能にすると、プロパティの無効化の時間が増加して、パフォーマンスに影響します。

RegisterClassHandler は慎重に使用する

RegisterClassHandler を呼び出すと、インスタンスの状態を保存することができます。ただし、このハンドラはすべてのインスタンスで呼び出されるということを認識しておく必要があります。これはパフォーマンス上問題になる可能性があります。RegisterClassHandler を使用するのは、アプリケーションでインスタンスの状態を保存する必要がある場合だけにしてください。

DependencyProperty の既定値は登録時に設定する

既定値を必要とする DependencyProperty を作成するときには、DependencyPropertyRegister メソッドにパラメータとして渡される既定のメタデータを使用してその値を設定します。コンストラクタや要素の各インスタンスでプロパティ値を設定するのではなく、この方法を使用するようにしてください。

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.FillmyBrush を割り当てると、その Rectangle オブジェクトを指すデリゲートが SolidColorBrush オブジェクトの Changed イベントに追加されます。このため、次のコードを使用しても、myRect は実際にはガベージ コレクションの対象にはなりません。

myRectangle = null;

この場合、myRectangle はまだ myBrush によって維持されています。myBrush は、Changed イベントを発生させるときに myRectangle にコールバックします。myBrush を新しい RectangleFill プロパティに割り当てても、myBrush にイベント ハンドラがさらに追加されるだけです。

この種のオブジェクトをクリーンアップするための推奨される方法としては、Fill プロパティから Brush を削除します。これにより、Changed イベント ハンドラも削除されます。

myRectangle.Fill = null;
myRectangle = null;

ユーザー インターフェイスの仮想化

WPF もデータ バインド子コンテンツを自動的に "仮想化する" StackPanel 要素のバリエーションを提供します。ここで、"仮想化" は、どの項目を画面に表示するかに基づいて、UIElements のサブセットを多数のデータ項目から生成する手法を指します。特定の時間に画面に UI 要素が少ししか表示されていない場合に多数の UI 要素を生成すると、メモリおよびプロセッサの両方に負荷がかかります。(VirtualizingPanel によって提供される機能を介する) VirtualizingStackPanel は、表示される項目を計算して、ItemsControl (ListBoxListView など) の ItemContainerGenerator と共に機能することで、表示される項目から UIElements の作成のみを行います。

パフォーマンスの最適化の一環として、これらの項目のビジュアル オブジェクトは、画面に表示される場合にのみ生成または維持されます。既にコントロールの表示可能領域にないビジュアル オブジェクトは削除される可能性があります。これをデータの仮想化と混同しないようにしてください。データの仮想化では、すべてのデータ オブジェクトがローカル コレクションに存在するのではなく、データ オブジェクトが必要に応じてストリームされます。

次の表は、5000 の TextBlock 要素を StackPanelVirtualizingStackPanel に追加して描画した場合の経過時間を示しています。このシナリオの測定値は、テキスト文字列を ItemsControl オブジェクトの ItemsSource プロパティに割り当ててからパネル要素にそのテキスト文字列が表示されるまでの時間を表します。

ホスト パネル レンダリング時間 (ミリ秒)

StackPanel

3210

VirtualizingStackPanel

46

アプリケーション リソース

WPF では、アプリケーション リソースを共有して、同じような種類の要素の間で外観や動作の一貫性を維持することができます。リソースの詳細については、「リソースの概要」を参照してください。

リソースの共有

アプリケーションでカスタム コントロールを使用していて、ResourceDictionary (または XAML Resources ノード) でリソースを定義している場合は、Application オブジェクトまたは Window オブジェクトのレベルで定義するか、カスタム コントロールの既定のテーマで定義することをお勧めします。カスタム コントロールの ResourceDictionary でリソースを定義すると、そのコントロールのすべてのインスタンスにパフォーマンスの影響が及びます。たとえば、負荷の高いブラシ操作がカスタム コントロールとその多くのインスタンスのリソース定義の一部として定義されていると、アプリケーションの作業セットが大幅に増大します。

これを実際の例で考えてみましょう。WPF を使用してトランプ ゲームを開発するとします。ほとんどのトランプ ゲームでは、それぞれ異なる面を持つ 52 枚のカードが必要です。そのためにカード カスタム コントロールを実装することにして、そのリソースで 52 のブラシを定義します (各ブラシはそれぞれカードの面を表します)。メイン アプリケーションでは、最初にこのカード カスタム コントロールの 52 のインスタンスを作成します。カード カスタム コントロールの各インスタンスでは、それぞれ Brush オブジェクトの 52 のインスタンスが生成されます。その結果、全部で 52 × 52 の Brush オブジェクトがアプリケーションに存在することになります。ブラシをカード カスタム コントロールのリソースから Application オブジェクトまたは Window オブジェクトのレベルに移動するか、カスタム コントロールの既定のテーマで定義すると、カード コントロールの 52 のインスタンスの間で 52 のブラシが共有されるようになるため、アプリケーションの作業セットが縮小されます。

ブラシはコピーせずに共有する

同じ Brush オブジェクトを使用する複数の要素がある場合は、ブラシを XAML でインラインで定義するのではなく、リソースとして定義して参照するようにします。この方法を使用すると、1 つのインスタンスを作成してそれを再利用することができます。一方、ブラシを XAML でインラインで定義した場合は、各要素に対して新しいインスタンスが作成されます。

この例を次のマークアップ サンプルに示します。

<StackPanel.Resources>
  <LinearGradientBrush x:Key="myBrush" StartPoint="0,0.5" EndPoint="1,0.5" Opacity="0.5">
    <LinearGradientBrush.GradientStops>
      <GradientStopCollection>
        <GradientStop Color="GoldenRod" Offset="0" />
        <GradientStop Color="White" Offset="1" />
      </GradientStopCollection>
    </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
</StackPanel.Resources>

<!-- Non-shared Brush object. -->
<Label>
  Label 1
  <Label.Background>
    <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5" Opacity="0.5">
      <LinearGradientBrush.GradientStops>
        <GradientStopCollection>
          <GradientStop Color="GoldenRod" Offset="0" />
          <GradientStop Color="White" Offset="1" />
        </GradientStopCollection>
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </Label.Background>
</Label>

<!-- Shared Brush object. -->
<Label Background="{StaticResource myBrush}">Label 2</Label>
<Label Background="{StaticResource myBrush}">Label 3</Label>

可能な限り静的リソースを使用する

静的リソースは、既に定義されたリソースに対する参照を検索することによって、任意の XAML プロパティ属性の値を指定します。そのリソースに関する検索動作は、コンパイル時の検索に似ています。

一方、動的リソースは、初期コンパイル中に一時的な式を作成し、それによって、要求されたリソース値がオブジェクトを構成するために実際に必要になるまで、リソースに関する検索を遅延します。そのリソースに関する検索動作は、実行時検索に似ています。これはパフォーマンスに影響します。アプリケーションでは可能な限り静的リソースを使用し、動的リソースを使用するのは必要な場合だけにしてください。

次のマークアップ サンプルでは、この両方の種類のリソースが使用されています。

<StackPanel.Resources>
  <SolidColorBrush x:Key="myBrush" Color="Teal"/>
</StackPanel.Resources>

<!-- StaticResource reference -->
<Label Foreground="{StaticResource myBrush}">Label 1</Label>

<!-- DynamicResource reference -->
<Label Foreground="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">Label 2</Label>

テキスト

WPF では、機能豊富なユーザー インターフェイス (UI) コントロールを使用してテキスト コンテンツを表示できます。一般に、テキスト レンダリングは次の 3 つの層に分けることができます。

  1. Glyphs オブジェクトと GlyphRun オブジェクトを直接使用する。

  2. FormattedText オブジェクトを使用する。

  3. TextBlock オブジェクトや FlowDocument オブジェクトなどの高レベルのコントロールを使用する。

グリフ レベルのテキスト レンダリング

Windows Presentation Foundation (WPF) は、ユーザーが書式設定後にテキストを途中受信および永続化できるように、Glyphs に直接アクセスできるグリフ レベルのマークアップを含む高度なテキストをサポートします。これらの機能によって、次の各シナリオにおけるさまざまなテキスト レンダリングの要件が満たされます。

  • 固定形式ドキュメントの画面表示。

  • 印刷シナリオ。

    • デバイス プリンタ言語としての Extensible Application Markup Language (XAML)。

    • Microsoft XPS Document Writer。

    • Win32 アプリケーションから固定形式に出力される以前のプリンタ ドライバ。

    • 印刷スプール形式。

  • 前のバージョンの Windows のクライアントやその他のコンピューティング デバイスを含む固定形式のドキュメントの表示。

メモメモ :

Glyphs および GlyphRun は、固定形式のドキュメントの表示および印刷シナリオのために設計されています。Windows Presentation Foundation (WPF) には、LabelTextBlock などの一般的なレイアウトおよびユーザー インターフェイス (UI) シナリオのための要素がいくつか用意されています。レイアウトおよび UI シナリオの詳細については、「Windows Presentation Foundation の文字体裁」を参照してください。

Extensible Application Markup Language (XAML) の Glyphs オブジェクトのプロパティを定義する方法を次の例に示します。Glyphs オブジェクトは、XAML の GlyphRun の出力を表します。この例は、Arial、Courier New、および Times New Roman フォントが、ローカル コンピュータの C:\WINDOWS\Fonts フォルダにインストールされていることを前提としています。

<!-- The example shows how to use a Glyphs object. -->
<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  >

   <StackPanel Background="PowderBlue">

      <Glyphs
         FontUri             = "C:\WINDOWS\Fonts\TIMES.TTF"
         FontRenderingEmSize = "100"
         StyleSimulations    = "BoldSimulation"
         UnicodeString       = "Hello World!"
         Fill                = "Black"
         OriginX             = "100"
         OriginY             = "200"
      />

   </StackPanel>
</Page>

DrawGlyphRun の使用

カスタム コントロールでグリフを描画するには、DrawGlyphRun メソッドを使用します。

WPF には、FormattedText オブジェクトを使用する低レベルのカスタム テキスト書式設定サービスも用意されています。Windows Presentation Foundation (WPF) でテキストを描画するための最も効率的な方法は、GlyphsGlyphRun を使用してグリフ レベルでテキスト コンテンツを生成する方法です。ただし、効率性が高い反面、TextBlockFlowDocument などの Windows Presentation Foundation (WPF) コントロールに組み込まれているような使いやすい豊富なテキスト書式設定機能は利用できません。

FormattedText オブジェクト

FormattedText オブジェクトを使用すると、複数行のテキストを描画できます。このテキストでは、テキスト内の各文字を個々に書式設定できます。詳細については、「書式設定されたテキストの描画」を参照してください。

書式設定されたテキストを作成するには、FormattedText コンストラクタを呼び出して FormattedText オブジェクトを作成します。最初の書式設定済みテキスト文字列を作成したら、書式スタイルの範囲を適用できます。アプリケーションで独自のレイアウトを実装するには、TextBlock などのコントロールを使用するより、FormattedText オブジェクトの方が適しています。FormattedText オブジェクトの詳細については、「書式設定されたテキストの描画」を参照してください。

FormattedText オブジェクトは、低レベルのテキスト書式設定機能を提供します。複数の書式スタイルを 1 つ以上の文字に適用できます。たとえば、SetFontSize メソッドと SetForegroundBrush メソッドの両方を呼び出して、テキストの最初の 5 文字の書式設定を変更できます。

FormattedText オブジェクトを作成して描画するコード例を次に示します。

protected override void OnRender(DrawingContext drawingContext)
{
    string testString = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor";

    // Create the initial formatted text string.
    FormattedText formattedText = new FormattedText(
        testString,
        CultureInfo.GetCultureInfo("en-us"),
        FlowDirection.LeftToRight,
        new Typeface("Verdana"),
        32,
        Brushes.Black);

    // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears.
    formattedText.MaxTextWidth = 300;
    formattedText.MaxTextHeight = 240;

    // Use a larger font size beginning at the first (zero-based) character and continuing for 5 characters.
    // The font size is calculated in terms of points -- not as device-independent pixels.
    formattedText.SetFontSize(36 * (96.0 / 72.0), 0, 5);

    // Use a Bold font weight beginning at the 6th character and continuing for 11 characters.
    formattedText.SetFontWeight(FontWeights.Bold, 6, 11);

    // Use a linear gradient brush beginning at the 6th character and continuing for 11 characters.
    formattedText.SetForegroundBrush(
                            new LinearGradientBrush(
                            Colors.Orange,
                            Colors.Teal,
                            90.0),
                            6, 11);

    // Use an Italic font style beginning at the 28th character and continuing for 28 characters.
    formattedText.SetFontStyle(FontStyles.Italic, 28, 28);

    // Draw the formatted text string to the DrawingContext of the control.
    drawingContext.DrawText(formattedText, new Point(10, 0));
}

FlowDocument、TextBlock、および Label コントロール

WPF には、画面にテキストを描画するための複数のコントロールが含まれています。各コントロールは、異なるシナリオを対象にしており、それぞれに一連の機能と制限があります。

FlowDocument は TextBlock や Label よりパフォーマンスへの影響が大きい

一般的に、ユーザー インターフェイス (UI) で短い文を使用するなど、限定的なテキストのサポートが必要な場合は、TextBlock 要素を使用する必要があります。最小限のテキスト サポートが必要な場合には、Label が使用できます。FlowDocument 要素は、コンテンツの多彩な表示をサポートする再フロー可能なドキュメントのコンテナです。したがって、TextBlock コントロールや Label コントロールを使用する場合に比べてパフォーマンスへの影響が大きくなります。

FlowDocument の詳細については、「フロー ドキュメントの概要」を参照してください。

FlowDocument 内で TextBlock を使用しない

TextBlock 要素は UIElement から派生します。Run 要素は TextElement から派生するため、UIElement 派生オブジェクトを使用する場合に比べてパフォーマンスへの負荷が小さくなります。FlowDocument でテキスト コンテンツを表示する場合には、可能であれば、TextBlock ではなく Run を使用するようにしてください。

FlowDocument 内のテキスト コンテンツを設定する 2 つの方法を次のマークアップ サンプルに示します。

<FlowDocument>

  <!-- Text content within a Run (more efficient). -->
  <Paragraph>
    <Run>Line one</Run>
  </Paragraph>

  <!-- Text content within a TextBlock (less efficient). -->
  <Paragraph>
    <TextBlock>Line two</TextBlock>
  </Paragraph>

</FlowDocument>

テキスト プロパティの設定に Run を使用しない

一般に、TextBlock 内で Run を使用すると、Run オブジェクトを一切明示的に使用しない場合に比べてパフォーマンスへの影響が大きくなります。Run を使用してテキスト プロパティを設定している場合は、代わりに直接 TextBlock で設定するようにしてください。

次のマークアップ サンプルは、テキスト プロパティ (ここでは FontWeight プロパティ) を設定するためのこの 2 つの方法を示しています。

<!-- Run is used to set text properties. -->
<TextBlock>
  <Run FontWeight="Bold">Hello, world</Run>
</TextBlock>

<!-- TextBlock is used to set text properties, which is more efficient. -->
<TextBlock FontWeight="Bold">
  Hello, world
</TextBlock>

次の表は、1000 の TextBlock オブジェクトを表示する際に明示的な Run を使用する場合と使用しない場合の負荷の比較を示しています。

TextBlock の種類 作成時間 (ミリ秒) レンダリング時間 (ミリ秒)

Run でテキスト プロパティを設定

146

540

TextBlock でテキスト プロパティを設定

43

453

Label.Content プロパティへのデータ バインディングは使用しない

たとえば、頻繁に String ソースから更新される Label オブジェクトがあったとします。このような場合、Label 要素の Content プロパティを String ソース オブジェクトにデータ バインドすると、パフォーマンスが低下する可能性があります。String オブジェクトは不変であり、変更できないため、ソース String が更新されるたびに、古い String オブジェクトが破棄されて新しい String が再作成されます。その結果、Label オブジェクトの ContentPresenter でも、新しい String を表示するために、古いコンテンツが破棄されて新しいコンテンツが再生成されます。

この問題の解決方法は単純です。Label がカスタム ContentTemplate 値に設定されていない場合は、LabelTextBlock に置き換えて、その Text プロパティをソース文字列にデータ バインドします。

データ バインドされたプロパティ 更新時間 (ミリ秒)

Label.Content

835

TextBlock.Text

242

Hyperlink オブジェクトはインラインレベルのフロー コンテンツ要素であり、これを使用すると、フロー コンテンツ内でハイパーリンクをホストできます。

複数の Hyperlink 要素の使用を最適化するには、それらを同じ TextBlock 内にまとめます。これにより、アプリケーションで作成されるオブジェクトの数を最小限に抑えることができます。たとえば、次のような複数のハイパーリンクを表示するとします。

MSN Home | My MSN

次のマークアップ例では、複数の TextBlock 要素を使用してこれらのハイパーリンクを表示しています。

<!-- Hyperlinks in separate TextBlocks. -->
<TextBlock>
  <Hyperlink TextDecorations="None" NavigateUri="http://www.msn.com">MSN Home</Hyperlink>
</TextBlock>

<TextBlock Text=" | "/>

<TextBlock>
  <Hyperlink TextDecorations="None" NavigateUri="http://my.msn.com">My MSN</Hyperlink>
</TextBlock>

次のマークアップ例は、1 つの TextBlock を使用してこれらのハイパーリンクをより効率的に表示する方法を示しています。

<!-- Hyperlinks combined in the same TextBlock. -->
<TextBlock>
  <Hyperlink TextDecorations="None" NavigateUri="http://www.msn.com">MSN Home</Hyperlink>
  
  <Run Text=" | " />
  
  <Hyperlink TextDecorations="None" NavigateUri="http://my.msn.com">My MSN</Hyperlink>
</TextBlock>

MouseEnter イベントが発生した場合にのみハイパーリンクの下線を表示する

TextDecoration オブジェクトを使用すると、テキストに視覚的な装飾を追加することができます。ただし、このオブジェクトのインスタンス化はパフォーマンスに影響を与える場合があります。Hyperlink 要素を広く使用する場合は、MouseEnter イベントのようなイベントが発生したときにだけ下線を表示することを検討してください。詳細については、「方法 : ハイパーリンク付き文字装飾を使用する」を参照してください。

MouseEnter で表示されるハイパーリンク

Hyperlinks displaying TextDecorations

下線付きおよび下線なしで定義されている Hyperlink のマークアップのサンプルを次に示します。

<!-- Hyperlink with default underline. -->
<Hyperlink NavigateUri="http://www.msn.com">
  MSN Home
</Hyperlink>

<Run Text=" | " />

<!-- Hyperlink with no underline. -->
<Hyperlink Name="myHyperlink" TextDecorations="None"
           MouseEnter="OnMouseEnter"
           MouseLeave="OnMouseLeave"
           NavigateUri="http://www.msn.com">
  My MSN
</Hyperlink>

次の表は、1000 の Hyperlink 要素を下線付きで表示した場合と下線なしで表示した場合のパフォーマンスの比較を示しています。

ハイパーリンク 作成時間 (ミリ秒) レンダリング時間 (ミリ秒)

下線付き

289

1130

下線なし

299

776

テキスト書式設定機能

WPF には、自動ハイフネーションなど、豊富なテキスト書式設定サービスが用意されています。これらのサービスはアプリケーションのパフォーマンスに影響する可能性があるため、必要な場合にのみ使用するようにしてください。

不要なハイフネーションを使用しない

自動ハイフネーションを使用すると、テキストの行のハイフンで区切る必要がある箇所が検索されます。TextBlock オブジェクトや FlowDocument オブジェクトで、ハイフンで区切る位置を追加することもできます。既定では、これらのオブジェクトでは自動ハイフネーション機能は無効になっています。この機能を有効にするには、オブジェクトの IsHyphenationEnabled プロパティを true に設定します。ただし、この機能を有効にすると、WPF でコンポーネント オブジェクト モデル (COM) 相互運用が開始されて、アプリケーションのパフォーマンスに影響する可能性があります。自動ハイフネーションは、必要な場合以外は使用しないことをお勧めします。

Figure の使用に注意する

Figure 要素は、コンテンツ ページ内の絶対位置に配置できるフロー コンテンツの一部を表します。Figure の位置が既に配置されているコンテンツと競合していると、場合によっては、ページ全体の書式が自動的に再設定されます。Figure 要素を隣どうしにまとめたり、固定ページ サイズのシナリオでコンテンツの上の方で宣言したりすることによって、不要な書式再設定が行われる可能性を最小限に抑えることができます。

最適な段落

FlowDocument オブジェクトの最適な段落機能は、スペースができるだけ均等に配分されるように段落を配置します。既定では、最適な段落機能は無効です。この機能を有効にするには、オブジェクトの IsOptimalParagraphEnabled プロパティを true に設定します。ただし、この機能を有効にするとアプリケーションのパフォーマンスに影響します。最適な段落機能は、必要な場合以外は使用しないことをお勧めします。

データ連結

Windows Presentation Foundation (WPF) データ バインディングは、アプリケーションがデータを提示し、データと対話するための簡単で一貫性のある方法を提供します。要素は、CLR オブジェクトや XML の形式のさまざまなデータ ソースのデータにバインドできます。データ バインディングの詳細については、「データ バインディングの概要」を参照してください。

データ バインディングの参照が解決されるしくみ

データ バインディングのパフォーマンスの問題に入る前に、Windows Presentation Foundation (WPF) のデータ バインディング エンジンがバインディングのオブジェクト参照をどのように解決するのかを説明します。

Windows Presentation Foundation (WPF) のデータ バインディングでは、任意の CLR オブジェクトをソースとして使用して、CLR オブジェクトのプロパティ、サブプロパティ、またはインデクサにバインドできます。バインディング参照は、Microsoft .NET Framework Version 3.0 のリフレクションか ICustomTypeDescriptor を使用して解決されます。バインディングのオブジェクト参照を解決するための 3 つの方法について、次に説明します。

1 つ目は、リフレクションを使用する方法です。この場合は、PropertyInfo オブジェクトを使用してプロパティの属性を検出し、プロパティ メタデータにアクセスします。ICustomTypeDescriptor インターフェイスを使用している場合は、データ バインディング エンジンはこのインターフェイスを使用してプロパティ値にアクセスします。ICustomTypeDescriptor インターフェイスは、オブジェクトに静的なプロパティのセットがない場合に特に便利です。

プロパティ変更通知を提供するには、INotifyPropertyChanged インターフェイスを実装することも、TypeDescriptor に関連付けられている変更通知を使用することもできます。ただし、プロパティ変更通知を実装するには INotifyPropertyChanged を使用することをお勧めします。

ソース オブジェクトが CLR オブジェクトでソース プロパティが CLR プロパティの場合、Windows Presentation Foundation (WPF) のデータ バインディング エンジンでは、最初にソース オブジェクトでリフレクションを使用して TypeDescriptor を取得してから PropertyDescriptor を照会する必要があります。パフォーマンスの観点から見た場合、このリフレクション操作のシーケンスには多くの時間が費やされる可能性があります。

オブジェクト参照を解決するための 2 つ目の方法は、ソース オブジェクトが INotifyPropertyChanged インターフェイスを実装する CLR オブジェクトで、ソース プロパティが CLR プロパティの場合に使用されます。この場合、データ バインディング エンジンは、直接ソースの型でリフレクションを使用して必要なプロパティを取得します。この方法も最適な方法とは言えませんが、最初の方法よりは作業セットの要件の負荷が小さくなります。

オブジェクト参照を解決するための 3 つ目の方法は、ソース オブジェクトが DependencyObject で、ソース プロパティが DependencyProperty の場合に使用されます。この場合、データ バインディング エンジンはリフレクションを使用する必要はありません。代わりに、プロパティ エンジンとデータ バインディング エンジンが共同でプロパティ参照を個別に解決します。これが、データ バインディングに使用されているオブジェクト参照を解決するための最適な方法です。

これらの方法を使用して 1000 の TextBlock 要素の Text プロパティをデータ バインドした場合の速度の比較を次の表に示します。

TextBlock の Text プロパティのバインド先 バインディング時間 (ミリ秒) レンダリング時間 -- バインディングを含む (ミリ秒)

CLR オブジェクトのプロパティ

115

314

INotifyPropertyChanged を実装する CLR オブジェクトのプロパティ

115

305

DependencyObjectDependencyProperty

90

263

大きな CLR オブジェクトへのバインディング

何千ものプロパティを持つ 1 つの CLR オブジェクトへのデータ バインディングは、パフォーマンスに大きな影響を及ぼします。この影響を最小限に抑えるには、その 1 つのオブジェクトを複数の CLR オブジェクトに分割して、個々のオブジェクトのプロパティの数を減らします。次の表は、1 つの大きな CLR オブジェクトへのデータ バインディングと複数の小さなオブジェクトへのデータ バインディングのバインディング時間とレンダリング時間を示しています。

1000 の TextBlock オブジェクトのデータ バインディングのバインド先 バインディング時間 (ミリ秒) レンダリング時間 -- バインディングを含む (ミリ秒)

1000 のプロパティを持つ 1 つの CLR オブジェクト

950

1200

1 つのプロパティを持つ 1000 の CLR オブジェクト

115

314

ItemsSource へのバインディング

ListBox に表示する従業員のリストを保持する CLR List オブジェクトがあったとします。この 2 つのオブジェクトの間に対応関係を作成するには、従業員のリストを ListBoxItemsSource プロパティにバインドします。では、グループに新しい従業員が加わった場合はどうすればよいでしょうか。バインドされた ListBox 値にその新しい人物を挿入するには、ただその人物を従業員リストに追加すれば、その変更が自動的にデータ バインディング エンジンに認識されると思われるかもしれません。しかし、実際にはそうはならず、その変更は ListBox に自動的には反映されません。これは、CLR List オブジェクトはコレクション変更イベントを自動的に発生させないからです。ListBox に変更を反映するには、従業員のリストを再作成し、再度 ListBoxItemsSource プロパティに割り当てる必要があります。この解決方法で問題は解決されますが、パフォーマンスへの影響はきわめて大きくなります。ListBoxItemsSource を新しいオブジェクトに割り当てるたびに、ListBox はまず前の項目を削除し、その後にリスト全体を再生成します。ListBox が複雑な DataTemplate にマップされている場合、パフォーマンスへの影響はさらに大きくなります。

この問題は、従業員リストを ObservableCollection にすることによってきわめて効率的に解決することができます。ObservableCollection オブジェクトは変更通知を発生させるため、それをデータ バインディング エンジンで受け取ることができます。このイベントにより、リスト全体を再生成する必要なく、ItemsControl の項目を追加または削除できます。

次の表は、項目を 1 つ追加した場合に ListBox の更新にかかる時間を示しています (UI の仮想化はオフ)。1 行目の数字は、CLR List オブジェクトが ListBox 要素の ItemsSource にバインドされている場合の経過時間を表しています。2 行目の数字は、ObservableCollectionListBox 要素の ItemsSource にバインドされている場合の経過時間を表しています。ObservableCollection のデータ バインディング方法を使用すると時間を大幅に節約できることがわかります。

ItemsSource のデータ バインディングのバインド先 1 項目の更新時間 (ミリ秒)

CLR List オブジェクト

1656

ObservableCollection

20

IEnumerable ではなく IList を ItemsControl にバインドする

ItemsControl オブジェクトに IList または IEnumerable のどちらでもバインドできる場合は、IList オブジェクトを使用するようにしてください。IEnumerableItemsControl にバインドすると、WPF でラッパー IList オブジェクトが作成されます。これにより、追加のオブジェクトの不要なオーバーヘッドが発生するため、パフォーマンスに影響します。

データ バインディングのためだけに CLR オブジェクトを XML に変換しない

WPF では、XML コンテンツへのデータ バインディングが可能です。ただし、XML コンテンツへのデータ バインディングは、CLR オブジェクトへのデータ バインディングに比べて低速です。CLR オブジェクトのデータをデータ バインディングのためだけに XML に変換しないでください。

その他のパフォーマンスの推奨事項

ブラシの Opacity と要素の Opacity

Brush を使用して要素の FillStroke を設定するときには、要素の Opacity プロパティを設定するより Brush.Opacity 値を設定することをお勧めします。要素の Opacity プロパティを変更すると、WPF によって一時的なサーフェイスが作成される可能性があります。

オブジェクトへの移動

NavigationWindow オブジェクトは Window から派生し、コンテンツ ナビゲーションのサポートでウィンドウを拡張します。この拡張は、主に NavigationService と履歴を統合することによって行われます。NavigationWindow のクライアント領域は、統一リソース識別子 (URI) またはオブジェクトを指定することによって更新できます。この両方の方法を次のサンプルに示します。

private void buttonGoToUri(object sender, RoutedEventArgs args)
{
    navWindow.Source = new Uri("NewPage.xaml", UriKind.RelativeOrAbsolute);
}

private void buttonGoNewObject(object sender, RoutedEventArgs args)
{
    NewPage nextPage = new NewPage();
    nextPage.InitializeComponent();
    navWindow.Content = nextPage;
}

NavigationWindow オブジェクトには、そのウィンドウのユーザーのナビゲーションを記録する履歴があります。履歴の目的の 1 つは、ユーザーが自分の来た道を戻れるようにすることです。

統一リソース識別子 (URI) を使用して移動した場合、履歴には 統一リソース識別子 (URI) の参照のみが格納されます。したがって、ページに戻るたびにそのページが動的に再構築されることになり、ページの複雑さによってはかなりの時間がかかることもあります。この場合、履歴の格納の負荷は低い反面、ページの再構築にかかる時間が長くなる可能性があります。

オブジェクトを使用して移動した場合は、オブジェクトのビジュアル ツリー全体が履歴に格納されます。したがって、ページに戻るたびにページを再構築する必要はなく、ページがすぐに描画されます。この場合、履歴の格納の負荷は高くなりますが、ページの再構築にかかる時間は短くて済みます。

NavigationWindow オブジェクトを使用するときには、履歴のサポートがアプリケーションのパフォーマンスにどのように影響するのかを念頭に置いておく必要があります。詳細については、「ナビゲーションの概要」を参照してください。

大きな 3D サーフェイスのヒット テスト

大きな 3D サーフェイスのヒット テストは、CPU 消費の面でパフォーマンスへの影響が非常に大きくなります。3D サーフェイスがアニメーション化されている場合には特にその傾向が強くなります。そのようなサーフェイスでヒット テストを行う必要がない場合は、ヒット テストを無効にしてください。UIElement から派生したオブジェクトでは、IsHitTestVisible プロパティを false に設定することによってヒット テストを無効にできます。

ScrollBarVisibility=Auto は使用しない

HorizontalScrollBarVisibility プロパティと VerticalScrollBarVisibility プロパティでは、可能な限り ScrollBarVisibilityAuto を使用しないようにしてください。これらのプロパティは、RichTextBoxScrollViewerTextBox、および ListBox の各オブジェクトで定義されています (ListBox では添付プロパティとして定義されています)。代わりに、ScrollBarVisibilityDisabledHidden、または Visible に設定します。

Auto 値は、スペースが限られていて、スクロール バーを必要なときにだけ表示する場合に使用するための値です。たとえば、数百行のテキストを含む TextBox などではなく、30 の項目を含む ListBox などで役に立ちます。

WPF のパフォーマンス ツールとリソース

WPF は、アプリケーションの実行時の動作を分析したり、適用可能なパフォーマンス最適化の種類を決定したりできるパフォーマンス プロファイリング ツール スイートを提供します。Windows SDK ツール (WPFPerf) に含まれる 5 つのパフォーマンス プロファイリング ツールを次の表に示します。

ツール 説明

イベント トレース

イベントの分析とイベント ログ ファイルの生成に使用します。

Perforator

レンダリング動作の分析に使用します。

トレース ビューア

WPF ユーザー インターフェイス形式で Event Tracing for Windows (ETW) ログ ファイルを記録、表示、および参照します。

ビジュアル プロファイラ

ビジュアル ツリーの要素による WPF サービス (レイアウトやイベント処理など) の使用状況に関するプロファイリングで使用します。

作業セット ビューア

アプリケーションの作業セット特性を分析するために使用します。

ビジュアル プロファイラ ツール スイートでは、パフォーマンス データを多彩なグラフィカル ビューで示します。このスクリーンショットでは、ビジュアル プロファイラの [CPU Usage] セクションに、レンダリングやレイアウトなど、WPF サービスのオブジェクトの使用率の詳細が示されています。

ビジュアル プロファイラ表示出力

Visual Profiler display output

WPF パフォーマンス ツールの詳細については、「WPF のパフォーマンス プロファイリング ツール」を参照してください。

XamlPad を使用したビジュアル ツリーの表示

XAMLPad を使用してビジュアル ツリー階層を分析すると、コントロール テンプレート拡張のしくみがわかります。この知識は、作成するユーザー インターフェイス設計のパフォーマンスへの負荷とトレードオフを理解するうえで役に立ちます。

XamlPad は、現在定義されているXAML コンテンツに対応するビジュアル ツリーを表示および探索するためのオプションを提供します。ビジュアル ツリーを表示するには、メニュー バーの [Show Visual Tree] ボタンをクリックします。XamlPad の [Visual Tree Explorer] パネルのビジュアル ツリー ノードに展開された XAML コンテンツを次の図に示します。

XamlPad の Visual Tree Explorer パネル

Visual Tree Explorer panel in XamlPad

XamlPad の [Visual Tree Explorer] パネルでは、LabelTextBox、および Button の各コントロールが別々のビジュアル オブジェクト階層を表示していることに注意してください。これは、WPF コントロールにそのコントロールのビジュアル ツリーを含む ControlTemplate があるためです。コントロールを明示的に参照すると、コントロールのビジュアル階層が暗黙的に参照されます。ビジュアル オブジェクトとビジュアル ツリーの詳細については、「Windows Presentation Foundation のグラフィックス レンダリングの概要」を参照してください。

[Visual Tree Explorer] の項目のプロパティ設定を表示するには、その項目を選択します。[Visual Tree Explorer] パネルの下の [Property Tree Explorer] パネルに、選択したビジュアル オブジェクトの現在のプロパティ設定が表示されます。

XamlPad の Property Tree Explorer パネル

Property Tree Explorer

詳細については、「XAMLPad」を参照してください。

WPF のデバッグ トレース サポート

PresentationTraceSources クラスは、Windows Presentation Foundation (WPF) アプリケーションを対象とするデバッグ トレースをサポートします。トレースは、アプリケーションの進行状況を追跡できる診断システムです。トレース ステートメントでは、よく WriteLine メソッドが使用されるのと同じような方法で情報が報告されます。ただし、トレース ステートメントでは、構成ファイルを使用してオン/オフを切り替えることができます。また、出力をカスタマイズすることもできます。

.NET Framework 3.0 のその他の関連する診断クラスについては、System.Diagnostics を参照してください。

参照

関連項目

RenderOptions
RenderCapability

概念

グラフィックスの描画層
Windows Presentation Foundation のグラフィックス レンダリングの概要
レイアウト システム
要素ツリー
Drawing オブジェクトの概要
DrawingVisual オブジェクトの使用
依存関係プロパティの概要
Freezable オブジェクトの概要
リソースの概要
Windows Presentation Foundation のドキュメント
書式設定されたテキストの描画
Windows Presentation Foundation の文字体裁
データ バインディングの概要
ナビゲーションの概要

その他の技術情報

WPF のパフォーマンス プロファイリング ツール