Большие массивы и фрагментация LOH. Что такое принятая конвенция?
У меня есть другой активный вопрос ЗДЕСЬ относительно некоторых безнадежных проблем с памятью, которые могут включать фрагментацию LOH среди, возможно, других неизвестных.
Каков мой вопрос сейчас, каков принятый способ делать вещи? Если мое приложение должно быть выполнено в Visual C# и должно иметь дело с большими массивами до мелодии int[4000000], как я не могу быть обречен отказом сборщика мусора иметь дело с LOH?
Казалось бы, я вынужден делать любые большие массивы глобальными и никогда не использовать слово "новый" вокруг любого из них. Таким образом, у меня остались неблагодарные глобальные массивы с переменными "maxindex" вместо массивов аккуратного размера, которые передаются функциями.
Мне всегда говорили, что это плохая практика. Какая альтернатива есть?
Есть ли какая-то функция на мелодию System.GC.CollectLOH("Seriously")
? Есть ли какой-нибудь способ передать сборщик мусора на что-то другое, кроме System.GC?
В любом случае, каковы общепринятые правила работы с большими (>85 КБ) переменными?
5 ответов
Во-первых, сборщик мусора собирает LOH, поэтому не стоит сразу пугаться его превосходства. LOH собирается, когда поколение 2 собирается.
Разница в том, что LOH не уплотняется, что означает, что если у вас есть объект с большим временем жизни, то вы фактически разделите LOH на две части - область до и область после этого объекта. Если такое поведение продолжает происходить, вы можете столкнуться с ситуацией, когда пространство между долгоживущими объектами недостаточно велико для последующих назначений, и.NET приходится выделять все больше и больше памяти для размещения ваших больших объектов, то есть LOH становится фрагментированным.
Теперь, сказав это, LOH может уменьшиться в размере, если область на его конце полностью свободна от живых объектов, поэтому единственная проблема заключается в том, что вы оставляете объекты там в течение длительного времени (например, продолжительность приложения).
Стратегии, позволяющие избежать фрагментации LOH:
- Избегайте создания больших объектов, которые торчат вокруг. По сути, это просто означает большие массивы или объекты, которые обертывают большие массивы (например, MemoryStream, который оборачивает байтовый массив), поскольку ничто иное не является настолько большим (компоненты сложных объектов хранятся отдельно в куче, поэтому редко бывают очень большими). Также следите за большими словарями и списками, так как они используют массив внутри.
- Остерегайтесь двойных массивов - пороговое значение для этих поступлений в LOH намного, намного меньше - я не могу вспомнить точную цифру, но ее всего несколько тысяч.
- Если вам нужен MemoryStream, подумайте над созданием фрагментированной версии, которая использует несколько меньших массивов, а не один огромный массив. Вы также можете создать собственную версию IList и IDictionary, которые используют чанкинг, чтобы избежать попадания материала в LOH.
- Избегайте очень длинных вызовов Remoting, так как Remoting интенсивно использует MemoryStreams, которые могут фрагментировать LOH в течение продолжительности вызова.
- Остерегайтесь интернирования строк - по какой-то причине они хранятся в виде страниц на LOH и могут вызвать серьезную фрагментацию, если ваше приложение продолжает сталкиваться с новыми строками для интернирования, т.е. избегать использования string.Intern, если не известно, что набор строк конечен и полный набор встречается на ранних этапах жизни приложения. (Смотрите мой предыдущий вопрос.)
- Используйте Son of Strike, чтобы увидеть, что именно использует память LOH. Снова смотрите этот вопрос для получения подробной информации о том, как это сделать.
Изменить: порог LOH для двойных массивов, кажется, 8 КБ.
Это старый вопрос, но я думаю, что не повредит обновлять ответы изменениями, внесенными в.NET. Теперь возможно дефрагментировать кучу больших объектов. Очевидно, что первый выбор должен быть сделан, чтобы убедиться, что были сделаны лучшие дизайнерские решения, но сейчас приятно иметь этот вариант.
https://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx
"Начиная с.NET Framework 4.5.1, вы можете сжать кучу больших объектов (LOH), установив для свойства GCSettings.LargeObjectHeapCompactionMode значение GCLargeObjectHeapCompactionMode.CompactOnce перед вызовом метода Collect, как показано в следующем примере".
GCSettings можно найти в пространстве имен System.Runtime
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
Первое, что приходит на ум, это разделить массив на более мелкие, чтобы они не достигли памяти, необходимой для GC, чтобы вставить в него LOH. Вы можете разбить массивы на более мелкие, скажем, 10000, и создать объект, который будет знать, какой массив искать, основываясь на переданном вами индексаторе.
Сейчас я не видел код, но я также хотел бы спросить, зачем вам такой большой массив. Потенциально я бы посмотрел на рефакторинг кода, чтобы всю эту информацию не нужно было сразу хранить в памяти.
Вы ошибаетесь. Вам НЕ нужно иметь размер массива 4000000, и вам определенно не нужно вызывать сборщик мусора.
- Напишите свою собственную реализацию IList. Нравится "PagedList"
- Храните предметы в массивах из 65536 элементов.
- Создайте массив массивов для хранения страниц.
Это позволяет вам получить доступ практически ко всем вашим элементам только с ОДНИМ перенаправлением. И, поскольку отдельные массивы меньше, фрагментация не является проблемой...
... если это так... тогда используйте страницы. Не выбрасывайте их при утилизации, поместите их в статический "PageList" и сначала вытяните их оттуда. Все это может быть прозрачно сделано в вашем классе.
Действительно хорошо, что этот список довольно динамичен в использовании памяти. Вы можете изменить размер массива держателей (перенаправитель). Даже если это не так, это около 512 КБд на страницу только.
Массивы второго уровня имеют в основном 64 КБ на байт - что составляет 8 байт для класса (512 КБ на страницу, 256 КБ на 32-битной) или 64 КБ на структуру байта.
Технически:
Превратите int[] в int[][]
Решите, 32 или 64 бит лучше, чем вы хотите;) Как преимущества, так и недостатки.
Работа с ОДНЫМ большим массивом, подобным этому, неприемлема в любом языке - если вам это нужно, то... в основном... выделяйте при запуске программы и никогда не создавайте заново. Единственное решение.
Это старый вопрос, но с.NET Standard 1.1 (.NET Core, .NET Framework 4.5.1+) есть другое возможное решение:
С помощью ArrayPool<T>
в System.Buffers
package, мы можем объединить массивы, чтобы избежать этой проблемы.
Добавляю уточнение к ответу выше, с точки зрения того, как проблема может возникнуть. Фрагментация LOH зависит не только от долгоживущих объектов, но если у вас есть ситуация, когда существует несколько потоков, и каждый из них создает большие списки, идущие на LOH, то вы можете иметь ситуацию, когда первый поток необходимо увеличить свой список, но следующий непрерывный бит памяти уже занят списком из второго потока, поэтому среда выполнения будет выделять новую память для первого списка потоков, оставляя за собой довольно большую дыру. Это то, что происходит в настоящее время в одном проекте, который я унаследовал, и поэтому, несмотря на то, что LOH составляет приблизительно 4,5 МБ, среда выполнения имеет в общей сложности 117 МБ свободной памяти, но самый большой сегмент свободной памяти составляет 28 МБ.
Другой способ сделать это без использования нескольких потоков: если в какой-то цикл добавлено несколько списков, и каждый из них расширяется за пределы первоначально выделенной для него памяти, то каждый перепрыгивает через другой, расширяясь за пределы выделенного пространства.,
Полезная ссылка: https://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/
Все еще ища решение для этого, один из вариантов может заключаться в использовании каких-либо объединенных объектов и запросе из пула при выполнении работы. Если вы имеете дело с большими массивами, то другим вариантом является разработка собственной коллекции, например, коллекции коллекций, чтобы у вас не было только одного огромного списка, а разбить его на более мелкие списки, каждый из которых обходится без LOH.