Csharp/C#教程:如何避免长寿命字符串导致第2代垃圾收集分享


如何避免长寿命字符串导致第2代垃圾收集

我有一个应用程序,我将日志字符串保存在循环缓冲区中。 当日志变满时,对于每个新插入,旧的字符串将被释放用于垃圾收集,然后它们在第2代内存中。 因此,最终将发生第2代GC,我想避免。

我试图将字符串编组成一个结构。 令人惊讶的是,我仍然得到第2代GC:s。 结构似乎仍然保留了对字符串的一些引用。 完整控制台应用程序 任何帮助赞赏。

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(s => s); TestPerformance(s => new FixedString(s)); TestPerformance(s => s); Console.WriteLine(); } Console.ReadKey(); } private static void TestPerformance(Func 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  s); TestPerformance(s => s); Console.WriteLine(); } Console.ReadKey(); } private static void TestPerformance(Func 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 

string字段的struct在这里有所不同应该不足为奇: string字段始终只是对托管堆上对象的引用 – 特别是某个string对象。 该string仍然存在,最终仍会导致GC2。

“修复”这个问题的唯一方法就是不要把它作为一个对象; 并且唯一的方法(不完全超出托管内存)是使用fixed缓冲区:

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

这里, 每个结构实例FixedString都有200个字节为数据保留。 str只是char*的相对偏移量,表示此预留的开始。 但是,使用它很棘手 – 并且需要unsafe代码。 另请注意,无论您是否确实要存储3个字符或170,每个FixedString保留相同的空间量。为避免内存问题,您可能需要使用null-teriminator,或分别存储有效负载长度。

请注意,在.NET 4.5中, 支持可以使这些值具有相当大的数组(例如, FixedString[] ) – 但请注意,您不希望经常复制数据。 为避免这种情况,您可能希望始终允许数组中的备用空间(因此您不要仅仅为了添加一个项目而复制整个数组),并通过ref处理单个项目,即

 FixedString[] data = ... int index = ... ProcessItem(ref data[index]); void ProcessItem(ref FixedString item) { // ... } 

这里的item直接与数组中的元素对话 – 我们没有在任何时候复制数据。

现在我们只有一个对象 – 数组本身。

  const int BufferSize = 1000000; 

你的缓冲区太大了,因此能够存储一个字符串引用太长时间,并允许它们被提升到第一代。 尝试使用缓冲区大小可提供此解决方案:

  const int BufferSize = 180000; 

不再有GC(2)集合。

你可以从中推断出gen#1堆大小。 虽然这个测试程序很难做到,但字符串大小变化太大。 无论如何,在真实应用程序中都需要进行手动调整。

虽然我喜欢Marc Gravell和Hans Passant的答案(一如既往)……

您可以微调GC以便同时运行,从而避免冻结时间。 在这里阅读它

使用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更快)。

上述就是C#学习教程:如何避免长寿命字符串导致第2代垃圾收集分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注---计算机技术网(www.ctvol.com)!

 Type Time GC(0) GC(1) GC(2) String 4647 131 108 23 StringBuilder 2600 94 0 0 UnsafeFixedString 5135 161 0 0 

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/cdevelopment/954424.html

(0)
上一篇 2021年11月20日
下一篇 2021年11月20日

精彩推荐