2013-09-23 2 views
6

私は、循環文字列にログ文字列を保存するアプリケーションを持っています。ログがいっぱいになると、新しい挿入ごとに古い文字列がガベージコレクションのために解放され、次にそれらは第2世代のメモリに格納されます。したがって、最終的に第2世代のGCが発生しますが、これは避けたいものです。2世代のガベージコレクションを引き起こす長いリビング文字列の回避方法

文字列を構造体にマーシャリングしようとしました。驚いたことに、私はまだジェネレーション2 GCを取得します。構造体はまだ文字列への参照を保持しているようです。下のコンソールアプリケーションを完了してください。どんな助けもありがたい。

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication 
{ 
    class Program 
    { 

     [StructLayout(LayoutKind.Sequential)] 
     public struct FixedString 
     { 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] 
      private string str; 

      public FixedString(string str) 
      { 
       this.str = str; 
      } 
     } 

     [StructLayout(LayoutKind.Sequential)] 
     public struct UTF8PackedString 
     { 
      private int length; 

      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] 
      private byte[] str; 

      public UTF8PackedString(int length) 
      { 
       this.length = length; 
       str = new byte[length]; 
      } 

      public static implicit operator UTF8PackedString(string str) 
      { 
       var obj = new UTF8PackedString(Encoding.UTF8.GetByteCount(str)); 
       var bytes = Encoding.UTF8.GetBytes(str); 
       Array.Copy(bytes, obj.str, obj.length); 
       return obj; 
      } 
     } 

     const int BufferSize = 1000000; 
     const int LoopCount = 10000000; 

     static void Main(string[] args) 
     { 
      Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", 
       "Type".PadRight(20), "Time", "GC(0)", "GC(1)", "GC(2)"); 
      Console.WriteLine(); 
      for (int i = 0; i < 5; i++) 
      { 
       TestPerformance<string>(s => s); 
       TestPerformance<FixedString>(s => new FixedString(s)); 
       TestPerformance<UTF8PackedString>(s => s); 
       Console.WriteLine(); 
      } 
      Console.ReadKey(); 
     } 

     private static void TestPerformance<T>(Func<string, T> func) 
     { 
      var buffer = new T[BufferSize]; 
      GC.Collect(2); 
      Stopwatch stopWatch = new Stopwatch(); 
      var initialCollectionCounts = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; 
      stopWatch.Reset(); 
      stopWatch.Start(); 
      for (int i = 0; i < LoopCount; i++) 
       buffer[i % BufferSize] = func(i.ToString()); 
      stopWatch.Stop(); 
      Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", 
       typeof(T).Name.PadRight(20), 
       stopWatch.ElapsedMilliseconds, 
       (GC.CollectionCount(0) - initialCollectionCounts[0]), 
       (GC.CollectionCount(1) - initialCollectionCounts[1]), 
       (GC.CollectionCount(2) - initialCollectionCounts[2]) 
      ); 
     } 
    } 
} 

編集:更新に必要な作業を行いUnsafeFixedStringとコード:自分のコンピュータ上の

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication 
{ 
    class Program 
    { 
     public unsafe struct UnsafeFixedString 
     { 
      private int length; 

      private fixed char str[256]; 

      public UnsafeFixedString(int length) 
      { 
       this.length = length; 
      } 

      public static implicit operator UnsafeFixedString(string str) 
      { 
       var obj = new UnsafeFixedString(str.Length); 
       for (int i = 0; i < str.Length; i++) 
        obj.str[i] = str[i];     
       return obj; 
      } 
     } 

     const int BufferSize = 1000000; 
     const int LoopCount = 10000000; 

     static void Main(string[] args) 
     { 
      Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", 
       "Type".PadRight(20), "Time", "GC(0)", "GC(1)", "GC(2)"); 
      Console.WriteLine(); 
      for (int i = 0; i < 5; i++) 
      { 
       TestPerformance(s => s); 
       TestPerformance<UnsafeFixedString>(s => s); 
       Console.WriteLine(); 
      } 
      Console.ReadKey(); 
     } 

     private static void TestPerformance<T>(Func<string, T> func) 
     { 
      var buffer = new T[BufferSize]; 
      GC.Collect(2); 
      Stopwatch stopWatch = new Stopwatch(); 
      var initialCollectionCounts = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; 
      stopWatch.Reset(); 
      stopWatch.Start(); 
      for (int i = 0; i < LoopCount; i++) 
       buffer[i % BufferSize] = func(String.Format("{0}", i)); 
      stopWatch.Stop(); 
      Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", 
       typeof(T).Name.PadRight(20), 
       stopWatch.ElapsedMilliseconds, 
       (GC.CollectionCount(0) - initialCollectionCounts[0]), 
       (GC.CollectionCount(1) - initialCollectionCounts[1]), 
       (GC.CollectionCount(2) - initialCollectionCounts[2]) 
      ); 
     } 
    } 
} 

出力は次のとおりです。

Type     Time GC(0) GC(1) GC(2) 

String     5746 160  71  19 
UnsafeFixedString  5345 418  0  0 
+0

第2世代のガベージコレクションを最初から避けたいのはなぜですか? – PVitt

+0

アプリケーションにはリアルタイムの要件があります。 GC(2)は完全なフリーズを引き起こす。 –

+1

文字列の代わりにbyte [](encoding.utf8.getbytes())というメッセージを格納するとどうなるでしょうか? –

答えて

7

それはとstruct驚くべきことではありませんフィールドはここで違いがあります:stringフィールドはです。常にを参照しています管理されたヒープ上のオブジェクト、具体的にはstringオブジェクトのどこかに格納されます。 stringはまだ存在し、最終的にGC2を引き起こします。

これを「修正」する唯一の方法は、オブジェクトとして持っていないことです。全く同じです。そして唯一の方法は、(マネージメモリの外に完全に行かなくても)fixedバッファを使用することを行うには:ここでは

public unsafe struct FixedString 
{ 
    private fixed char str[100]; 
} 

すべての構造体のインスタンスFixedStringは、データ用に予約さ200バイトです。 strは、単にこの予約の開始を示すchar*への相対的なオフセットです。しかし、はこれでを処理するのは難しく、全体を通してunsafeコードが必要です。また、FixedStringは、実際に3文字または170を格納するかどうかにかかわらず、同じ量の領域を予約します。メモリの問題を回避するには、ヌルテミネータを使用するか、ペイロード長を個別に格納する必要があります。

.NET 4.5では、<gcAllowVeryLargeObjects>のサポートにより、そのような値(たとえば、FixedString[])の適切なサイズの配列を持つことができますが、データを頻繁にコピーしたくないことに注意してください。それを避けるために、あなたは常にitemここで(あなたが一つだけのアイテムを追加するために配列全体をコピーしないでください)配列内の予備スペースを可能にし、refを経由して個々の項目で作業、すなわち

FixedString[] data = ... 
int index = ... 
ProcessItem(ref data[index]); 

void ProcessItem(ref FixedString item) { 
    // ... 
} 

したいと思いますの配列と直接話しています。データはいつでもコピーされていません。

ここでは、オブジェクト - アレイ自体が1つしかありません。

+1

ありがとう、Marc!ちょうどそれが働いた。 「安全でない」と「固定」のキーワードが違いを生み出しました。私はそれに答えてコードを更新します。私は私の思考エラーにも気づくと思います。元の例で使用した注釈はマーシャリングのためのものであり、.netがメモリ内の構造体をどのように編成するかを指導しません。 –

2
const int BufferSize = 1000000; 

バッファーが大きすぎるため、文字列参照を長時間格納することができ、gen#1を超えて昇格させることができます。バッファサイズを実験すると、次の解決策が得られます。

const int BufferSize = 180000; 

これ以上のGC(2)コレクションはありません。

これからgen#1ヒープサイズを推測できます。このテストプログラムでは行うことは困難ですが、文字列のサイズは可変です。いずれにしても、実際のアプリでハンドチューニングが必要になります。

+0

このアプリでは、ログが十分に大きく、エントリが第2世代に進むのに十分長く存続する必要があります。これが私がシミュレーションしたかったものです。ありがとう! –

1

私はあなたが同時に実行ので、凍結時間を避けるために、曲調にGCを細かくすることができます...

を(いつものように)、マルクGravellとハンスアンパッサン答えを言っていますが。 について読むhere

+0

私はワークステーション+コンカレントがワークステーションOSのデフォルトモードだと思って、スレッドのいくつかが数100ミリ秒間フリーズしてしまいます。ありがとう! –

0

StringBuilderのバッファを使用すると、unsafe fixed char[]のアプローチとまったく同じことになります。しかし、最初に割り当てた文字列の長さの潜在的な柔軟性を持たせてください(当然、そういう文字列、つまりStringBuilderの基礎となるchar[]はガベージコレクションの対象になります)。実用的である)。また、独自の文字列長管理を行う必要もありません。

private static void TestPerformance2() 
{ 
    var buffer = new StringBuilder[BufferSize]; 
    // Initialize each item of the array. This is no different than what 
    // unsafe struct is. 
    for (int i = 0; i < BufferSize; i++) 
    { 
     buffer[i] = new StringBuilder(256); 
    } 

    GC.Collect(2); 
    Stopwatch stopWatch = new Stopwatch(); 
    var initialCollectionCounts = new int[] { GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2) }; 
    stopWatch.Reset(); 
    stopWatch.Start(); 
    for (int i = 0; i < LoopCount; i++) 
    { 
     buffer[i % BufferSize].Clear(); // Or use .Length = 0;, which is what the Clear() method does internally. 

     buffer[i % BufferSize].AppendFormat("{0}", i); 
    } 
    stopWatch.Stop(); 
    Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", 
     typeof(StringBuilder).Name.PadRight(20), 
     stopWatch.ElapsedMilliseconds, 
     (GC.CollectionCount(0) - initialCollectionCounts[0]), 
     (GC.CollectionCount(1) - initialCollectionCounts[1]), 
     (GC.CollectionCount(2) - initialCollectionCounts[2]) 
    ); 
} 

そして二倍の速さの結果、(あなたも、配列の初期化が含まれるまでストップウォッチを移動することができますし、それはまだ速くUnsafeFixedStringよります)。

Type     Time GC(0) GC(1) GC(2) 

String     4647 131  108  23 
StringBuilder   2600 94  0  0 
UnsafeFixedString  5135 161  0  0 
関連する問題