7

我在这里跟进这个问题

我遇到的问题是我有一些来自 MSMQ 的大型对象,主要是字符串。我已将内存问题缩小到在大对象堆 (LOH) 中创建的这些对象,因此将其分段(在分析器的帮助下确认了这一点)。

在我上面发布的问题中,我得到了一些解决方法,主要是将 String 拆分为我所做的 char 数组的形式。

我面临的问题是,在字符串处理结束时(以任何形式),我需要将该字符串发送到另一个我无法控制的系统。所以我正在考虑以下解决方案,将这个字符串放在 LOH 中:

  1. 将其表示为每个小于 85k 的 char 数组的数组(要放置在 LOH 中的对象的阈值)
  2. 在发送端压缩它(即在我们这里讨论的接收器系统中接收它之前)并仅在将它传递到第三方系统之前对其进行解压缩。

无论我做什么 - 一种或另一种方式 - 字符串都必须是完整的(没有字符数组或压缩)。

我被困在这里了吗?我在想,如果在这里使用托管环境是一个错误,我们是否应该硬着头皮去使用 C++ 类型的环境。

谢谢, 雅尼斯

编辑:我已将问题范围缩小到此处发布代码

穿过的大弦被放置在 LOH 中。从收到消息的位置开始,我已经删除了每个处理模块,并且内存消耗趋势保持不变。

所以我想我需要改变这个 WorkContext 在系统之间传递的方式。

4

3 回答 3

1

那么您的选择取决于第 3 方系统如何接收数据。如果您可以以某种方式流式传输到它,那么您不必一次性将其全部存储在内存中。如果是这种情况,那么压缩(如果数据易于压缩,这可能真的会帮助您的网络负载)非常好,因为您可以通过流解压缩并将其以块的形式推送到第 3 方系统。

如果您将琴弦分开以低于 LoH 阈值,那么当然会起作用。

如果不是,那么我仍然主张在 MSMQ 消息上拆分有效负载,然后使用预分配和重用字节数组的内存池进行重新组装,然后再将其发送到客户端。Microsoft 有一个实现,您可以使用http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.buffermanager.aspx

我能想到的最后一个选项是在 C++ 中处理非托管代码中的 msmq 反序列化,并使用placement new 将字符串反序列化到其中创建您自己的自定义大块内存池。您可以通过确保池缓冲区足够长的消息来保持它相对简单,而不是试图变得聪明和动态,这很难。

于 2011-10-21T00:29:11.360 回答
1

您可以尝试使用 a StringBuilder(使用类似绳索实现的 4.0 版本)流式传输值。

此示例必须Release模式下执行并Start Without Debugging附带 (CTRL-F5)。Debug模式和GC都Start Debugging太乱了。

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;
    }
}

我直接读取Message.BodyStreaman 中的消息,XmlReader然后转到我需要的元素,并使用块读取数据XmlReader.ReadValueChunk

最后我无处使用string对象。唯一的大块内存是Message.

于 2011-10-21T08:53:39.653 回答
0

您也许可以实现一个类(调用它LargeString),该类重用先前分配的字符串并保留它们的一小部分集合。

由于字符串通常是不可变的,因此您必须通过不安全的指针杂耍来完成所有更改和新分配。将字符串传递给接收者后,您需要手动将其标记为免费以供重用。不同的消息长度也可能是一个问题,除非接收者可以处理太长的消息,或者你有一个不同长度的字符串集合。

可能不是一个好主意,但也许比用 C++ 重写所有内容要好。

于 2011-10-17T07:57:06.823 回答