Должен ли я вызывать GC.Collect сразу после использования кучи больших объектов для предотвращения фрагментации - PullRequest
8 голосов
/ 19 декабря 2009

Мое приложение хорошо выполняет двоичную сериализацию и сжатие больших объектов. Несжатый сериализированный набор данных составляет около 14 МБ. Сжатый это около 1,5 МБ. Я обнаружил, что всякий раз, когда я вызываю метод serialize в моем наборе данных, мой счетчик производительности кучи больших объектов увеличивается с менее чем 1 МБ до примерно 90 МБ. Я также знаю, что в относительно загруженной системе, обычно после некоторого времени работы (дней), когда этот процесс сериализации происходит несколько раз, было известно, что приложение выбрасывает исключения памяти, когда этот метод сериализации вызывается, даже если кажется, много памяти. Я предполагаю, что проблема заключается в фрагментации (хотя я не могу сказать, что уверен на 100%, я довольно близок)

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

Кроме этого нелепого распределения в 90 МБ, я не думаю, что у меня есть что-то еще, что использует потерю LOH. Это выделение 90 МБ также является относительно редким (примерно каждые 4 часа). Конечно, у нас все еще будет массив размером 1,5 МБ и, возможно, некоторые другие меньшие сериализованные объекты.

Есть идеи?

Обновление в результате хороших ответов

Вот мой код, который делает работу. Я на самом деле пытался изменить это, чтобы сжать сериализацию WHILE, чтобы сериализация сериализировалась в поток одновременно, и я не получил намного лучший результат. Я также пытался предварительно выделить поток памяти до 100 МБ и пытаться использовать один и тот же поток два раза подряд, LOH в любом случае увеличивается до 180 МБ. Я использую Process Explorer для мониторинга. Это безумие. Я думаю, что я собираюсь попробовать идею UnmanagedMemoryStream дальше.

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

        byte[] bytes;
        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =
        new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();            
        System.IO.MemoryStream memStream = new System.IO.MemoryStream();
        serializer.Serialize(memStream, obj);
        bytes = CompressionHelper.CompressBytes(memStream.ToArray());
        memStream.Dispose();
        return bytes;

Обновление после попытки двоичной сериализации с UnmanagedMemoryStream

Даже если я сериализую в UnmanagedMemoryStream, LOH поднимется до того же размера. Кажется, что независимо от того, что я делаю, называется BinaryFormatter для сериализации этого большого объекта будет использовать LOH. Что касается предварительного распределения, это не очень помогает. Скажем, я предварительно выделил, скажем, я предварительно выделил 100 МБ, затем я сериализировал, он будет использовать 170 МБ. Вот код для этого. Даже проще, чем приведенный выше код

BinaryFormatter serializer  = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(1024100);
GC.Collect();
serializer.Serialize(memoryStream, assetDS);
*1024* GC.Collect () в середине находится только для обновления счетчика производительности LOH. Вы увидите, что он выделит правильные 100 МБ. Но затем, когда вы вызываете сериализацию, вы заметите, что она, кажется, добавляет это поверх 100, которые вы уже выделили.

Ответы [ 6 ]

4 голосов
/ 19 декабря 2009

Остерегайтесь того, как классы и потоки коллекций, такие как MemoryStream, работают в .NET. У них есть базовый буфер, простой массив. Всякий раз, когда буфер коллекции или потока выходит за пределы выделенного размера массива, массив перераспределяется, теперь в два раза больше предыдущего размера.

Это может вызвать много копий массива в LOH. Ваш набор данных объемом 14 МБ начнет использовать LOH на 128 КБ, затем займет еще 256 КБ, затем еще 512 КБ и так далее. Последний, фактически используемый, будет около 16 МБ. LOH содержит их сумму около 30 МБ, только один из которых используется в действительности.

Сделайте это три раза без коллекции gen2, и ваш LOH увеличится до 90 МБ.

Избегайте этого, предварительно выделив буфер ожидаемого размера. MemoryStream имеет конструктор, который занимает начальную емкость. Так делают все классы коллекции. Вызов GC.Collect () после того, как вы обнулили все ссылки, может помочь снять блокировку LOH и очистить эти промежуточные буферы, за счет слишком быстрого засорения кучи gen1 и gen2.

3 голосов
/ 05 марта 2010

К сожалению, единственный способ исправить это - разбить данные на куски, чтобы не выделять большие куски в LOH. Все предложенные здесь ответы были хорошими и должны были сработать, но это не так. Похоже, что двоичная сериализация в .NET (с использованием .NET 2.0 SP2) делает свое собственное маленькое волшебство под капотом, который не дает пользователям контролировать распределение памяти.

Тогда ответьте на вопрос "это вряд ли сработает". Когда дело доходит до использования сериализации .NET, лучше всего сериализовать большие объекты небольшими порциями. Для всех других сценариев упомянутые выше ответы являются отличными.

2 голосов
/ 19 декабря 2009

90МБ оперативной памяти не много.

Старайтесь не звонить в GC.Collect, если у вас нет проблем. Если у вас есть проблема, и вам не удастся решить проблему, попробуйте вызвать GC.Collect и посмотреть, решена ли ваша проблема.

0 голосов
/ 14 января 2010

Я согласен с некоторыми другими постерами, что вы можете попробовать использовать трюки для работы с .NET Framework вместо того, чтобы заставлять его работать с вами через GC.Collect.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * [*].

0 голосов
/ 14 января 2010

Не беспокойтесь о размере LOH. Беспокойство по поводу выделения / освобождения LOH. .Net очень глупо относится к LOH - вместо того, чтобы размещать объекты LOH вдали от обычной кучи, он размещается на следующей доступной странице виртуальной машины. У меня есть 3D-приложение, которое много выделяет / освобождает как LOH, так и обычные объекты - в результате (как видно из отчета о дампе DebugDiag) страницы с малой кучей и большой кучей в конечном итоге чередуются по всей ОЗУ, пока не будет больших кусков из приложений осталось 2 гб виртуального пространства. Решение, когда это возможно, состоит в том, чтобы выделить один раз то, что вам нужно, а затем не освобождать его - использовать его в следующий раз.

Используйте DebugDiag для анализа вашего процесса. Посмотрите, как адреса виртуальных машин постепенно приближаются к отметке адреса 2 ГБ. Затем внесите изменение, которое предотвратит это.

0 голосов
/ 19 декабря 2009

Если вам действительно нужно использовать LOH для чего-то вроде службы или чего-то, что должно работать в течение длительного времени, вам нужно использовать буферные пулы, которые никогда не освобождаются и которые вы в идеале можете выделить при запуске. Это означает, что для этого вам, конечно, придется самостоятельно управлять памятью.

В зависимости от того, что вы делаете с этой памятью, вам также может понадобиться p / Invoke для собственного кода для выбранных частей, чтобы избежать необходимости вызывать какой-либо .NET API, который заставляет вас помещать данные в недавно выделенное пространство в LOH.

Хорошая статья о проблемах: http://blogs.msdn.com/maoni/archive/2004/12/19/327149.aspx

Я бы посчитал вас очень удачливым, если бы ваш трюк с ГК сработал, и он действительно сработал бы, если в системе в то же самое время ничего не происходит. Если у вас параллельная работа, это лишь немного задержит неизбежное.

Также читайте в документации о GC.Collect.IIRC, GC.Collect (n) только говорит, что он собирает не дальше, чем поколение n - не то, что он действительно когда-либо ПОЛУЧАЕТ до поколения n.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...