dotTrace 2020.1ヘルプ

アプリのパフォーマンスとメモリのトラフィックを最適化する

警告!このチュートリアルはdotTrace 2016.2のために書かれました。一部のUIコントロール(フィルタなど)は、dotTrace 2016.3で大幅に再設計されています。例:分析サブジェクトフィルターのすべてのコントロールをイベントフィルターに移動しました。

メモリトラフィックがアプリケーションのパフォーマンスに大きく影響することはよく知られています。トラフィックが多いほど、アプリのパフォーマンスは低下します。この問題は、アプリケーションがメモリを割り当てる頻度(パフォーマンスが低下していればほぼ自由です)ではなく、不要になったメモリをアプリケーションがどのように収集するかにあります。残念なことに、これを行うガベージコレクション(GC)メカニズムの利便性はコストがかかります。

まず、GCそのものにいくらかのCPU時間が必要です。例:その段階の1つは未使用オブジェクトの検出であり、オブジェクト参照グラフを構築する複雑な操作です。次に、Gen0およびGen1 GC *を実行するには、ガベージコレクタが管理対象ヒープの一部に排他的にアクセスする必要があります。これにより、「ブロッキングGC」をトリガーしたスレッドを除くすべての管理スレッドが一時停止されます。ユーザーインターフェースのスレッドも中断されるため、ユーザーはこれらの瞬間にUIがフリーズすることがあります。

そのため、常にアプリケーションを最適化してメモリトラフィックを削減し、GCがアプリケーションの応答性に与える影響を最小限に抑える必要があります。

このチュートリアルでは、タイムラインプロファイリングを使用して過剰なGCとその原因を検出する方法を学習します。

サンプルアプリケーション

タイムラインのプロファイリングを開始するチュートリアルと同じ基本サンプルアプリケーションを使用します。このアプリケーションは、テキストファイル内の行を逆にするために使用されます。 ABC => CBA

アプリのソースコードはgithub(英語)で入手できます。

t2 working app

ファイルの選択ボタンを使用すると、ユーザーは処理するテキストファイルを選択します。プロセスファイルボタンは、ファイル内の行を反転する別の BackgroundWorker スレッド(FileProcessing)を実行します。メインウィンドウの左隅にあるラベルに進捗が表示されます。処理が終了すると、ラベルにすべてのファイルが正常に処理されましたが表示されます。

以下のシナリオを考えてみましょう。アプリケーションをテストするとき、テキストファイルが期待どおりに高速に処理されないことがわかります。さらに、ファイル処理中にマイナーなラグが発生する場合もあります。

タイムラインプロファイリングを使用して、これらのパフォーマンスの欠点を分析してみましょう。

ステップ 1. プロファイラの実行とスナップショットの取得

  1. Visual Studioで MassFileProcessing.sln ソリューションを開きます。

  2. ReSharper | プロファイル | スタートアップ設定のパフォーマンスプロファイリングを実行しています...を選択してプロファイラを実行します。

  3. プロファイリング・タイプでは、タイムラインを選択します。

    t2 profiling config

  4. 実行をクリックします。dotTraceはアプリケーションを実行し、プロファイリングプロセスを制御するための特別なコントローラーウィンドウを表示します。

    t1 profiling controller
    私たちのアプリでパフォーマンスの課題を再現しようとしましょう。

  5. ファイルの選択をクリックし、Text Files フォルダーにあるアプリケーションに付属の5つのテキストファイルを選択します。

    t1 app

  6. ファイル処理を開始するには、プロセスファイルをクリックします。

  7. 処理が終了したら、コントローラーウィンドウでスナップショットを取得して待機するをクリックして、タイムラインプロファイリングスナップショットを収集します。スナップショットはVisual Studioの別のパフォーマンスプロファイラツールウィンドウで開きます。

  8. アプリケーションを閉じます。これにより、コントローラーウィンドウも閉じます。

ステップ 2. 分析開始

  1. パフォーマンスプロファイラツールウィンドウで、スレッドを表示するをクリックします。これにより、別のスレッドツールウィンドウにアプリケーションスレッドのリストが開きます。
    t2 performance profiler window
    dotTraceがいくつかの管理対象スレッドを検出したことがわかります。これらは、メインスレッド、バックグラウンドGCを実行するために使用されるガーベッジ・コレクションスレッド、およびファイルを処理するために使用されるFileProcessingスレッド*です。さらに、作業を行わない2つのスレッド、すなわちファイナライザスレッドと補助スレッドプールがあります。
  2. ファイルの処理が遅くなるにつれて、FileProcessingスレッドがファイルを処理する期間を拡大しましょう。これを行うには、スレッド図でマウスホイールを使用します。

    t2 threads zoomed
    これにより、可視時間間隔(1950 ミリ秒)だけフィルタが自動的に追加されます。このフィルタが他のフィルタに与える影響に注意してください。すべての値は、可視の時間範囲で再計算されます。スナップショットデータに適用されるフィルタは「すべてのスレッドの表示可能時間範囲内のすべての時間間隔を選択する」です。ファイルを処理するのに必要なおよその時間として2秒を覚えておきましょう。

  3. プロセス概要図(スレッドウィンドウの上部)で、GCバーを参照してください。多くのブロッキングGCがファイル処理中に実行されるようです。これは、大きなメモリトラフィックを示し、疑いなくアプリケーションのパフォーマンスに影響を与えます。

    このトラフィックの真の背景を見てみましょう。これには2つの方法があります。

    • GC中に実行されているスレッドとメソッドを特定します。これらは、これらのコレクションを切り替えたスレッドとメソッドでなければなりません。

    • メモリの大部分を割り当てる方法を特定します。ロジックは単純です:GCを切り替える主な理由は、メモリ割り当てのためにヒープのサイズを変更することです。メソッドがメモリを多く割り当てると、GCも多くトリガされます。

先を見れば、第2の方法ははるかに簡単で、少し信頼性が高いことがわかります。それにもかかわらず、教育目的のために、両方を試してみましょう。

ステップ 3. メモリトラフィックはどこから来ますか?ガベージコレクションの分析

  1. スレッドウィンドウ(またはパフォーマンスプロファイラウィンドウ)の上部にあるフィルタのリストで、ブロッキングGCフィルタのブロッキングGC値を選択します。結果のフィルタは"ブロッキングGCが発生するすべての表示されたスレッドの可視時間範囲内のすべての時間間隔を選択"になります。

    t2 blocking gc filter

  2. ここで、GCが発生するスレッドを特定しましょう.*例:これはメインスレッドと仮定できます。

    スレッド図のメインスレッドを選択します。結果として得られるフィルタは、"ブロッキングGCが発生するメインスレッドの可視時間範囲内のすべての時間間隔を選択"になりました。

    t2 main thread filter
  3. 開いて、スレッド状態フィルターを参照してください。ここでは、これらすべてのGCでメインスレッドが何をしたのかを示します。ほとんどの時間(97.3%)は待機でした。これは、GCがいくつかの他のスレッド(明らかにFileProcessingスレッド上)で行われ、メインスレッドがGCが終了するまで待たなければならないことを意味します。

    t2 thread state filter
    ここで、FileProcessingスレッドのどのメソッドがすべてのGCに対して責任があるのかを見てみましょう。

  4. メインスレッドでフィルタを削除します。これを行うには、スレッドリストの対応するチェックボックスをオフにします。

  5. 代わりに、リストのFileProcessingスレッドを選択します。結果として得られるフィルタは、"ブロッキングGCが発生するFileProcessingスレッドの可視時間範囲内のすべての時間間隔を選択する"になりました。

    t2 threadpool filter

  6. スレッド状態フィルターを参照してください。これは、FileProcessingスレッドが実行であったGC時間の99.1%を示しています。これは、このスレッドがこれらのGCを担当していることを確認します。

    t2 running filter

  7. スレッド状態実行を選択してください。

  8. パフォーマンスプロファイラツールウィンドウで、トップメソッドをクリックして、最も時間の長いユーザーメソッドのリストを表示します。
    t2 top methods filter
    適用されたすべてのフィルタを考慮して、トップメソッドはGCをトリガーするトップメソッド*を表示するようになりました。ご覧のとおり、StringReverser.Reverse はおそらくアプリケーションのメモリトラフィックのほとんどを生成する方法です。

ステップ 3. メモリトラフィックはどこから来ますか?メモリ割り当ての分析

次に、メモリトラフィックを分析する簡単な方法を試してみましょう。先に記述されていたように、最大のメモリ量を割り当てる方法を特定することが考えられます。

  1. パフォーマンスプロファイラまたはスレッドツールウィンドウで t2 clear all filters ボタンをクリックして、すべてのフィルタを削除します。

  2. イベントフィルタで、メモリの割り当て *を選択します。
    t2 memory allocation filter
  3. スレッドダイアグラムを参照してください。FileProcessingスレッドは、5882 MBの膨大なメモリを割り当てます。私たちのアプリで高メモリトラフィックに責任があることは間違いありません。

  4. トップメソッドを参照してください。今度は、割り当てられたメモリ量でメソッドをソートします。 StringReverser.Reverse メソッドは5496 MBではるかに遅れています。

    t2 top methods filter allocations

ステップ 4. コードを改善する

メソッドのコードを見て、何が間違っているのか調べてみましょう。

  1. トップメソッドStringReverser.Reverse メソッドを右クリックし、コンテキストメニューでコードに移動するを選択します。

    t2 top methods filter navigate to code

  2. コードを参照してください。この方法は、テキストファイルから行を逆にするために使用されるようです。入力時に文字列を受け取り、その逆の文字列を返します。

    public string Reverse(string line) { char[] charArray = line.ToCharArray(); string stringResult = null; for (int i = charArray.Length; i > 0; i--) { stringResult += charArray[i - 1]; } return stringResult; }
    どうやら、問題は文字列を逆にする方法です。つまり、文字列は不変型です。その内容は作成後に変更することはできません。つまり、+= 操作で stringResult に文字を追加するたびに、新しい文字列がメモリに割り当てられます。特別な StringBuilder クラスを使用するか、または文字列を char の配列として扱い、Reverseメソッドを使用する方がはるかに効果的です。2番目のオプションを試してみましょう。

  3. 下記のように StringReverser.Reverse の方法を修正してください。

    public string Reverse(string line) { char[] charArray = line.ToCharArray(); Array.Reverse(charArray); return new string(charArray); }

ステップ 5. 修正プログラムの確認

  1. ステップ 1に従って、ソリューションを再構築し、もう一度プロファイリングを実行します。

  2. スナップショットを開いたら、イベントメモリの割り当てに切り替えます。

  3. スレッドダイアグラムを参照してください。私たちの改善活動!メモリトラフィックは、FileProcessingスレッドの場合、ほぼ6 GBから166 MBに減少しました。

    t2 performance profiler window fixed

  4. FileProcessingがファイルを処理した時間間隔を拡大すると、ファイル処理が大幅にスピードアップしていることがわかります。現在、すべてのファイルを処理するのに約300ミリ秒かかります。これは修正前の2秒です。

最終更新日: 2020年6月19日