gpt4 book ai didi

c# - 如何避免长生命周期字符串导致第 2 代垃圾回收

转载 作者:太空狗 更新时间:2023-10-29 21:13:11 27 4
gpt4 key购买 nike

我有一个应用程序,我将日志字符串保存在循环缓冲区中。当日志变满时,对于每个新插入,旧字符串将被释放以进行垃圾收集,然后它们将进入第 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<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

最佳答案

struct 不足为奇用string字段在这里有所作为:a 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[] )成为可能 - 但请注意,您不想经常复制数据。为避免这种情况,您需要始终在数组中留出空闲空间(这样您就不会复制整个数组只是为了添加一项),并通过 ref 处理单个项。 ,即

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

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

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

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

关于c# - 如何避免长生命周期字符串导致第 2 代垃圾回收,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18957421/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com