Большая строка в куче больших объектов вызывает проблемы - но в любом случае она должна заканчиваться как строка - PullRequest
7 голосов
/ 17 октября 2011

Я слежу за этим вопросом здесь

Проблема в том, что у меня есть несколько больших объектов, поступающих из MSMQ, в основном из строк. Я сузил свои проблемы с памятью до того, что эти объекты были созданы в куче больших объектов (LOH) и, следовательно, фрагментированы (подтверждено с помощью профилировщика).

В вопросе, который я разместил выше, я нашел несколько обходных путей, главным образом, в форме разбиения строки на массивы символов, что я и сделал.

Проблема, с которой я сталкиваюсь, заключается в том, что в конце обработки строки (в любой форме) мне нужно отправить эту строку в другую систему, которую я не могу контролировать. Поэтому я думал о следующем решении, чтобы поместить эту строку в LOH:

  1. Представляет его как массив массивов символов менее 85 КБ каждый (порог объектов, которые должны быть помещены в LOH)
  2. Сожмите его на стороне отправителя (т. Е. Перед тем, как получить его в системе, о которой мы здесь говорим, которая является получателем), и распакуйте его только перед передачей в стороннюю систему.

Что бы я ни делал - так или иначе - строка должна быть завершена (без массивов или сжатых).

Я застрял здесь? Я думаю, если использование управляемой среды было ошибкой, и нужно ли кусать пулю и переходить на среду C ++.

Спасибо, Яннис

РЕДАКТИРОВАТЬ: Я сузил проблему до точно код размещен здесь

Большая проходящая строка помещается в LOH. Я удалил каждый модуль обработки с того места, где я получил сообщение, и тренд потребления памяти остается прежним.

Так что я думаю, мне нужно изменить способ передачи этого WorkContext между системами.

Ответы [ 3 ]

1 голос
/ 21 октября 2011

Ну, ваши варианты зависят от того, как сторонняя система получает данные.Если вы можете каким-то образом транслировать его, вам не нужно хранить все это в памяти за один раз.Если это так, то сжатие (которое, вероятно, действительно поможет вашей сети, если его легко сжимаемые данные) прекрасно, так как вы можете распаковать через поток и передать его в стороннюю систему порциями.

То же самоеконечно, сработало бы, если бы вы разбили свои строки так, чтобы они опустились ниже порога LoH.

Если нет, то я все равно рекомендовал бы разделить полезную нагрузку на сообщение MSMQ, а затем использовать пул памяти предварительно заблокированных и повторно используемых байтовых массивов дляповторная сборка перед отправкой клиенту.У Microsoft есть реализация, которую вы можете использовать http://msdn.microsoft.com/en-us/library/system.servicemodel.channels.buffermanager.aspx

Последний вариант, о котором я могу подумать, - это выполнить десериализацию msmq в неуправляемом коде на C ++ и создать свой собственный большой пул памяти большого блока, используя новое размещение для десериализацииструны в это.Вы можете сделать это относительно просто, обеспечив достаточность буферов пула для самого длинного сообщения, вместо того, чтобы пытаться быть умным и динамичным, что сложно.

1 голос
/ 21 октября 2011

Вы можете попробовать потоковую передачу значений, используя StringBuilder (версия 4.0, использующая реализацию в виде веревки).

Этот пример должен выполняться в режиме Release и с присоединенным Start Without Debugging (CTRL-F5). И режим Debug, и 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.BodyStream сообщения в XmlReader, а затем перехожу к нужным элементам и читаю данные кусками, используя XmlReader.ReadValueChunk

В конце концов, нигде я не использую string объектов. Единственный большой блок памяти - это Message.

0 голосов
/ 17 октября 2011

Возможно, вы могли бы реализовать класс (назовите его LargeString), который повторно использует ранее назначенные строки и сохраняет их небольшую коллекцию.

Поскольку строки обычно являются неизменяемыми, вам придется выполнять каждое изменениеи новое назначение небезопасным жонглированием указателем.После передачи строки получателю необходимо вручную пометить ее как свободную для повторного использования.Различные длины сообщений также могут быть проблемой, если только получатель не справится с сообщениями, которые слишком длинные, или у вас есть коллекция строк любой длины.

Возможно, это не очень хорошая идея, но, возможно, лучше, чем переписывать все вC ++.

...