C# 大型对象堆中的大型字符串会导致问题,但无论如何,它必须以字符串结束
我正在跟进 我的问题是,我有一些来自MSMQ的大型对象,主要是字符串。我已经将内存问题缩小到在大型对象堆(LOH)中创建的这些对象,并因此对其进行碎片化(在探查器的帮助下确认了这一点) 在我上面发布的问题中,我得到了一些解决方法,主要是将字符串拆分为字符数组,我做到了这一点 我面临的问题是,在字符串处理结束时(以任何形式),我需要将该字符串发送到另一个我无法控制的系统。因此,我想到了以下解决方案,将此字符串放置在LOH中:C# 大型对象堆中的大型字符串会导致问题,但无论如何,它必须以字符串结束,c#,.net,memory-management,c#-4.0,large-object-heap,C#,.net,Memory Management,C# 4.0,Large Object Heap,我正在跟进 我的问题是,我有一些来自MSMQ的大型对象,主要是字符串。我已经将内存问题缩小到在大型对象堆(LOH)中创建的这些对象,并因此对其进行碎片化(在探查器的帮助下确认了这一点) 在我上面发布的问题中,我得到了一些解决方法,主要是将字符串拆分为字符数组,我做到了这一点 我面临的问题是,在字符串处理结束时(以任何形式),我需要将该字符串发送到另一个我无法控制的系统。因此,我想到了以下解决方案,将此字符串放置在LOH中: 将其表示为每个小于85k的字符数组的数组(要放置在LOH中的对象的阈值)
因此,我想我需要改变这个工作上下文在系统之间传递的方式。您可能可以实现一个类(称之为
LargeString
),该类重用以前分配的字符串并保留它们的一小部分
由于字符串通常是不可变的,所以您必须通过不安全的指针杂耍来进行每次更改和新赋值。将字符串传递给接收器后,需要手动将其标记为free以供重用。不同的消息长度也可能是一个问题,除非接收者能够处理太长的消息,或者您有一个每个长度的字符串集合
可能不是一个好主意,但也许会打败重写C++中的一切。
< P>你的选择取决于第三方系统是如何接收数据的。如果你能以某种方式流到它,那么你不必一次就把它全部记在内存中。如果是这样的话,那么压缩(如果它的数据很容易压缩,那么它可能真的会帮助你的网络负载)是很好的,因为你可以通过一个流解压,并将它分块推到第三方系统 如果将字符串拆分到LoH阈值以下,当然也会有同样的效果 如果不是这样,我仍然主张在MSMQ消息上拆分负载,然后在将其发送到客户端之前,使用预先分配和重用的字节数组的内存池进行重新组装。Microsoft有一个可以使用的实现我可以想到的最后一个选项是处理C++中非托管代码中的MSMQ反序列化,并使用放置新的方法将自己的自定义大块内存池创建为反序列化字符串。您可以通过确保池缓冲区足以容纳尽可能长的消息来保持相对简单,而不是尝试变得聪明和动态,这很难。
您可以尝试使用StringBuilder
(使用绳索式实现的4.0版本)来流式传输值
此示例必须在Release
模式下执行,并附加Start而不调试(CTRL-F5)。Debug
模式和Start Debug
都会给GC带来太多麻烦
public class SerializableWork
{
// This is very often between 100-120k bytes. This is actually a String - not just for the purposes of this example
public String WorkContext { get; set; }
// This is quite large as well but usually less than 85k bytes. This is actually a String - not just for the purposes of this example
public String ContextResult { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Initial memory: {0}", GC.GetTotalMemory(true));
var sw = new SerializableWork { WorkContext = new string(' ', 1000000), ContextResult = new string(' ', 1000000) };
Console.WriteLine("Memory with objects: {0}", GC.GetTotalMemory(true));
using (var mq = new MessageQueue(@".\Private$\Test1"))
{
mq.Send(sw);
}
sw = null;
Console.WriteLine("Memory after collect: {0}", GC.GetTotalMemory(true));
using (var mq = new MessageQueue(@".\Private$\Test1"))
{
StringBuilder sb1, sb2;
using (var msg = mq.Receive())
{
Console.WriteLine("Memory after receive: {0}", GC.GetTotalMemory(true));
using (var reader = XmlTextReader.Create(msg.BodyStream))
{
reader.ReadToDescendant("WorkContext");
reader.Read();
sb1 = ReadContentAsStringBuilder(reader);
reader.ReadToFollowing("ContextResult");
reader.Read();
sb2 = ReadContentAsStringBuilder(reader);
Console.WriteLine("Memory after creating sb: {0}", GC.GetTotalMemory(true));
}
}
Console.WriteLine("Memory after freeing mq: {0}", GC.GetTotalMemory(true));
GC.KeepAlive(sb1);
GC.KeepAlive(sb2);
}
Console.WriteLine("Memory after final collect: {0}", GC.GetTotalMemory(true));
}
private static StringBuilder ReadContentAsStringBuilder(XmlReader reader)
{
var sb = new StringBuilder();
char[] buffer = new char[4096];
int read;
while ((read = reader.ReadValueChunk(buffer, 0, buffer.Length)) != 0)
{
sb.Append(buffer, 0, read);
}
return sb;
}
}
我在XmlReader
中直接读取消息的Message.BodyStream
,然后转到我需要的元素,使用XmlReader.ReadValueChunk
最后,我使用string
对象。唯一大的内存块是消息
是否真的要发送这么大的消息?如何将字符串发送到其他系统?你不能使用流吗?此外,使用C++可能没有帮助你,因为它的堆也会被碎片化。您是否尝试过使用服务器GC?您能将“字符串”流式传输到另一个系统吗?流式处理可以避免将其存储在一个连续的内存块中,这就是它最终出现在LOHI中的原因。我要补充的是,读到这里,他们会告诉4.0中已经“解决”的问题:-)谢谢Jens-问题是:工作节点从队列中获取工作(示例:)。当我分配最大的字符串(好主意!)时,我已经分配了WorkContext字符串。或者你是在建议将其改为char[]或其他形式,这样它就不会进入LOH,然后重新使用最大的结构?@Yannis:当然,你需要避免将长消息分配到任何地方的字符串。我认为你建议的两种解决方案都会奏效。您可以使用最大化来将结果传递给其他系统。