2012-03-12 12 views
28

多くのパフォーマンスの問題が発生しているWPFアプリケーションがあります。それらの最悪の場合は、アプリケーションが数秒間フリーズしてから、再び実行されることがあります。Cでのガベージコレクタの監視

私は現在、このフリーズが関連している可能性があるかどうかを確認するためにアプリケーションをデバッグしています。その原因の1つはガベージコレクタです。私のアプリケーションは非常に限られた環境で動作しているので、ガベージコレクタは実行時にすべてのマシンのリソースを使用し、アプリケーションには何も残さないと考えています。

この仮説を確認するには、ガベージコレクタの実行開始時と終了時にアプリケーションにどのように通知するかを説明する記事、Garbage Collection NotificationsGarbage Collection Notifications in .NET 4.0があります。私は通知を取得するには、以下のクラスを作成し、それらの記事に基づいてそう

、:

public sealed class GCMonitor 
{ 
    private static volatile GCMonitor instance; 
    private static object syncRoot = new object(); 

    private Thread gcMonitorThread; 
    private ThreadStart gcMonitorThreadStart; 

    private bool isRunning; 

    public static GCMonitor GetInstance() 
    { 
     if (instance == null) 
     { 
      lock (syncRoot) 
      { 
       instance = new GCMonitor(); 
      } 
     } 

     return instance; 
    } 

    private GCMonitor() 
    { 
     isRunning = false; 
     gcMonitorThreadStart = new ThreadStart(DoGCMonitoring); 
     gcMonitorThread = new Thread(gcMonitorThreadStart); 
    } 

    public void StartGCMonitoring() 
    { 
     if (!isRunning) 
     { 
      gcMonitorThread.Start(); 
      isRunning = true; 
      AllocationTest(); 
     } 
    } 

    private void DoGCMonitoring() 
    { 
     long beforeGC = 0; 
     long afterGC = 0; 

     try 
     { 

      while (true) 
      { 
       // Check for a notification of an approaching collection. 
       GCNotificationStatus s = GC.WaitForFullGCApproach(10000); 
       if (s == GCNotificationStatus.Succeeded) 
       { 
        //Call event 
        beforeGC = GC.GetTotalMemory(false); 
        LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "GC is about to begin. Memory before GC: %d", beforeGC); 
        GC.Collect(); 

       } 
       else if (s == GCNotificationStatus.Canceled) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was cancelled"); 
       } 
       else if (s == GCNotificationStatus.Timeout) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was timeout"); 
       } 
       else if (s == GCNotificationStatus.NotApplicable) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event was not applicable"); 
       } 
       else if (s == GCNotificationStatus.Failed) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC about to begin event failed"); 
       } 

       // Check for a notification of a completed collection. 
       s = GC.WaitForFullGCComplete(10000); 
       if (s == GCNotificationStatus.Succeeded) 
       { 
        //Call event 
        afterGC = GC.GetTotalMemory(false); 
        LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "GC has ended. Memory after GC: %d", afterGC); 

        long diff = beforeGC - afterGC; 

        if (diff > 0) 
        { 
         LogHelper.Log.InfoFormat("===> GC <=== " + Environment.NewLine + "Collected memory: %d", diff); 
        } 

       } 
       else if (s == GCNotificationStatus.Canceled) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was cancelled"); 
       } 
       else if (s == GCNotificationStatus.Timeout) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was timeout"); 
       } 
       else if (s == GCNotificationStatus.NotApplicable) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event was not applicable"); 
       } 
       else if (s == GCNotificationStatus.Failed) 
       { 
        LogHelper.Log.Info("===> GC <=== " + Environment.NewLine + "GC finished event failed"); 
       } 

       Thread.Sleep(1500); 
      } 
     } 
     catch (Exception e) 
     { 
      LogHelper.Log.Error(" ******************** Garbage Collector Error ************************ "); 
      LogHelper.LogAllErrorExceptions(e); 
      LogHelper.Log.Error(" ------------------- Garbage Collector Error --------------------- "); 
     } 
    } 

    private void AllocationTest() 
    { 
     // Start a thread using WaitForFullGCProc. 
     Thread stress = new Thread(() => 
     { 
      while (true) 
      { 
       List<char[]> lst = new List<char[]>(); 

       try 
       { 
        for (int i = 0; i <= 30; i++) 
        { 
         char[] bbb = new char[900000]; // creates a block of 1000 characters 
         lst.Add(bbb);    // Adding to list ensures that the object doesnt gets out of scope 
        } 

        Thread.Sleep(1000); 
       } 
       catch (Exception ex) 
       { 
        LogHelper.Log.Error(" ******************** Garbage Collector Error ************************ "); 
        LogHelper.LogAllErrorExceptions(e); 
        LogHelper.Log.Error(" ------------------- Garbage Collector Error --------------------- "); 
       } 
      } 


     }); 
     stress.Start(); 
    } 
} 

そして私は私のapp.configファイル(下記)にgcConcurrentオプションを追加しました:

<?xml version="1.0"?> 
<configuration> 
    <configSections> 
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net-net-2.0"/> 
    </configSections> 

    <runtime> 
    <gcConcurrent enabled="false" /> 
    </runtime> 

    <log4net> 
    <appender name="Root.ALL" type="log4net.Appender.RollingFileAppender"> 
     <param name="File" value="../Logs/Root.All.log"/> 
     <param name="AppendToFile" value="true"/> 
     <param name="MaxSizeRollBackups" value="10"/> 
     <param name="MaximumFileSize" value="8388608"/> 
     <param name="RollingStyle" value="Size"/> 
     <param name="StaticLogFileName" value="true"/> 
     <layout type="log4net.Layout.PatternLayout"> 
     <param name="ConversionPattern" value="%date [%thread] %-5level - %message%newline"/> 
     </layout> 
    </appender> 
    <root> 
     <level value="ALL"/> 
     <appender-ref ref="Root.ALL"/> 
    </root> 
    </log4net> 

    <appSettings> 
    <add key="setting1" value="1"/> 
    <add key="setting2" value="2"/> 
    </appSettings> 
    <startup> 
    <supportedRuntime version="v2.0.50727"/> 
    </startup> 

</configuration> 

しかし、アプリケーションが実行されるたびに、ガベージコレクタが実行されるという通知が送信されないように見えます。私はDoGCMonitoringにブレークポイントを入れました。条件(s == GCNotificationStatus.Succeeded)と(s == GCNotificationStatus.Succeeded)は決して満たされないため、これらのifsステートメントの内容は決して実行されません。

私は間違っていますか?

注:私はWPFと.NET Framework 3.5でC#を使用しています。

UPDATE 1

はAllocationTest方法で私のGCMonitorテストを更新しました。このメソッドは、テスト目的のみです。ガベージコレクタを強制的に実行するのに十分なメモリが割り当てられていることを確認したかっただけです。方法WaitForFullGCApproachとWaitForFullGCCompleteの復帰に新しい小切手と、DoGCMonitoring方法を更新しました

UPDATE 2

。これまで私が見たことから、私のアプリケーションは直接(s == GCNotificationStatus.NotApplicable)の状態になります。だから私はどこかに誤った設定があると思って、望みの結果が得られなくなってしまった。

GCNotificationStatus列挙型のドキュメントはhereです。

+3

実際にツールを使ってプロファイリングしようとしましたか、Windowsパフォーマンスモニタやwindbgのようなものですが、GCラッパーを作成するのではなく、 –

+0

GCが実行されていない可能性があります(まだ)。 AllocationTest()を表示できますか? –

+0

こんにちは、私は実際にプロファイリングツールを持っていますが、私が前に述べたフリーズの問題は、私のマシンではなく(私はそれを再現できません)、実稼働環境で起こっています。そして残念ながら、私は生産環境でプロファイリングツールを実行できません。 – Felipe

答えて

38

コードのどこにでもGC.RegisterForFullGCNotification(int,int)が表示されません。 WaitForFullGC[xxx]メソッドを使用しているようですが、通知のために登録することはありません。これはおそらくあなたがNotApplicableステータスを取得している理由です。

しかし、可能ではあるが、私は、GCがあなたの問題であることを疑うよ、私はに関するすべてのGCモードの存在であり、何が起こっているかを判断するための最良の方法を知って良いだろうと仮定します。 .NETには、サーバーとワークステーションの2つのガベージコレクションモードがあります。彼らはどちらも同じ未使用のメモリを収集しますが、実行される方法はそれほど多少異なります。

  • Serverバージョン - このモードでは、サーバー側のアプリケーションを使用している、そしてそれはこれらのシナリオのためのコレクションを最適化しようとするためにGCを伝えます。ヒープを複数のセクション(CPUあたり1つ)に分割します。 GCが起動すると、各CPU上で1つのスレッドが並列に実行されます。あなたは本当にこれがうまくいくために複数のCPUが必要です。サーバーのバージョンはGC用に複数のスレッドを使用していますが、以下に示す並行ワークステーションGCモードとは異なります。各スレッドは非並行バージョンのように動作します。

  • ワークステーションバージョン - このモードは、GCにクライアントサイドアプリケーションを使用していることを通知します。これは、サーバーのバージョンよりもリソースが限られているため、GCスレッドは1つだけです。ただし、ワークステーションのバージョンには、同時および非同時の2つの構成があります。

    • 同時 - これは、ワークステーションGCを使用するたびにデフォルトでオンになったバージョン(これはあなたのWPFアプリケーション用のケースになります)です。 GCは常にアプリケーションが実行されているときにコレクションのオブジェクトをマークする別のスレッドで実行されます。さらに、特定の世代でメモリを圧縮するかどうかを選択し、パフォーマンスに基づいてその選択を行います。圧縮が完了してもコレクションを実行するためにはすべてのスレッドをフリーズする必要がありますが、このモードを使用すると応答がほとんどないアプリケーションはほとんど見られません。これは、使用のためのより良いインタラクティブなエクスペリエンスを作り、コンソールやGUIアプリケーションに最適です。
    • 非並行 - これは、あなたがしたい場合は、使用するようにアプリケーションを設定することができたバージョンです。このモードでは、GCスレッドは、他のすべてのスレッドが中断されている全てながら、GCが開始されるまで、それが行くとゴミであるすべてのオブジェクトツリーをマーク眠るメモリ、コンパクト、それを解放します。これにより、という短時間の間にアプリケーションが応答しなくなることがあります。それがバックグラウンドで行われますので、

あなたは、同時コレクタ上の通知を登録することはできません。それはあなたのアプリケーションが同時コレクタを使用していない可能性があります(私はあなたがapp.configで無効にgcConcurrentを持って気づくが、それは唯一のテストのためだと思われます?)。そのような場合、重いコレクションがある場合は、アプリケーションがフリーズしているのを確かに見ることができます。これがコンカレントコレクタを作成した理由です。 GCモードの種類は、部分的にコードで設定でき、アプリケーション構成とマシン構成で完全に設定できます。

我々は我々のアプリケーションが使用している正確に何を把握するために何ができますか?実行時に、GCSettingsという静的クラス(System.Runtime)を照会することができます。あなたは、サーバーのバージョンでワークステーションを実行している場合GCSettings.IsServerGCはあなたを教えてくれますし、あなたが同時、非同時またはあなたがここで本当に適用されていませんコードで設定する必要が特別なものを使用している場合GCSettings.LatencyModeはあなたを伝えることができます。私はそれが始めるのに適していると思うし、マシン上でなぜそれがうまく動作しているのかを説明することができますが、生産はできません。

設定ファイルで

<gcConcurrent enabled="true|false"/>又は<gcServer enabled="true|false"/>ガベージコレクタのモードを制御します。これはマシン内のまたはのapp.configファイル(実行中のアセンブリの外側にあります)にあることに注意してください。あなたはまた、リモートでの.NETガベージコレクションの生産マシンのパフォーマンスカウンタにアクセスし、それらの統計を表示するには、Windowsパフォーマンスモニタを使用することができます%windir%\Microsoft.NET\Framework\[version]\CONFIG\

に位置している設定ファイル、。 Windows(ETW)のイベントトレースをすべてリモートで行うことができます。パフォーマンスモニタでは、.NET CLR Memoryオブジェクトが必要で、インスタンスリストボックスでアプリケーションを選択します。

+1

こんにちは、私はやった実際にGC.RegisterForFullGCNotification(int、int)行を忘れてしまった...私はちょっと愚かな気がする。とにかくあなたの非常に完全な答えをありがとう! – Felipe