Почему GC не освобождает память при выделении больших управляемых массивов?
От: Albeoris  
Дата: 02.02.14 12:07
Оценка: 1 (1) :)
Доброго времени суток!

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

Приложение использует .NET Framework, сборка AnyCPU.
Последовательная обработка файлов, загружает их полностью в память в виде массивов byte[].
Размер файлов колеблется от нескольких килобайт до нескольких мегабайт.
После N'го файла (>200) приложение падает с OutOfMemoryException.
Почему это происходит?

Общеизвестные факты:
1) Управляемые массивы больше 85000 байт выделяются в большой куче (LOH), которая до 4.5.1 не поддерживала сжатие (Compact), а данные в ней не могли перемещаться, и частое выделение (и удерживание) массивов приводит к её фрагментации.
2) Освобождением память, выделенной в управляемом коде занимается GC и делает это тогда, когда ему захочется, если явно не вызывается GC.Collect
3) Объекты, пережившие несколько сборок мусора и помещенные во второе поколение, в котором могут жить неопредленно долго после утраты всех ссылок.
4) Объекты во втором поколении должны освобождаться, когда система исчерпывает всю доступную память.

У меня есть множество предположений на этот счёт, но они не подкреплены фактами. И главное — я никак не могу вопросизвести ситуацию, в которой получил ошибку. А это очень важно.
Если кто-нибудь может дать ответ на этот вопрос — помогите, пожалуйста.

И связанный с этим вопрос:
Если бы у вас возникла необходимость в разработке стабильного и шустрого приложения, которое использует динамические буферы, как в примере выше и не может себе позволить вызывать GC.Collect после обнуления каждого, что бы вы использовали: управляемые массивы или Marshal.AllocHGlobal, обёрнутый в SafeBuffer? Почему? Если не сложно — приведите за и против, которые придут на ум.

Ещё раз прошу заметить, что ответы не предполагают изменять среду исполнения, версию .NET, железо или руки автора. D:
Нельзя заменять динамические массивы на статичные буферы или заменять загрузку всего файла в память на поточную обработку или MemoryMappedFile.

Всем спасибо за внимание, буду рад любой помощи! (=
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
gc managed memory
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: Evgeny.Panasyuk Россия  
Дата: 02.02.14 12:42
Оценка: -3
Здравствуйте, Albeoris, Вы писали:

A>Общеизвестные факты:

A>1) Управляемые массивы больше 85000 байт выделяются в большой куче (LOH), которая до 4.5.1 не поддерживала сжатие (Compact), а данные в ней не могли перемещаться, и частое выделение (и удерживание) массивов приводит к её фрагментации.

Не так давно подобная тема обсуждалась в КСВ.
Для сравнения, аллокатор VS2005 C++ на одинаковых сценариях
Автор: Evgeny.Panasyuk
Дата: 17.10.13
показывал на несколько порядков более качественный результат чем .NET'вский: получилось выделить RAM С++ 1915MiB vs C# 21MiB.

A>И связанный с этим вопрос:

A>Если бы у вас возникла необходимость в разработке стабильного и шустрого приложения, которое использует динамические буферы, как в примере выше и не может себе позволить вызывать GC.Collect после обнуления каждого, что бы вы использовали: управляемые массивы или Marshal.AllocHGlobal, обёрнутый в SafeBuffer? Почему? Если не сложно — приведите за и против, которые придут на ум.

Если выделять буферы одинакового размера, то внешняя фрагментация не страшна, даже без компактификации, даже на простейших аллокаторах.
Например, можно использовать не массивы, а так называемые "chunked arrays": буфер поделен на равные части, для которых есть O(1) индекс.
В C++ такая структура данных есть начиная с первого ISO 1998 — std::deque. Аналог для C# разыскивался в том КСВ топике
Автор: Evgeny.Panasyuk
Дата: 21.10.13
, но никакой вменяемой альтернативы знатоками C# продемонстрировано не было. Поэтому один из вариантов — сделать такой chunked array самому.
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 02.02.14 13:04
Оценка: :)
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Если выделять буферы одинакового размера, то внешняя фрагментация не страшна, даже без компактификации, даже на простейших аллокаторах.

Увы и ах. К сожалению, это невозможно. Буферы одинакового размера выделять нельзя из-за того, что максимальный размер данных может быть достаточно велик, а количество параллельных запросов — огромно.
EP>Например, можно использовать не массивы, а так называемые "chunked arrays": буфер поделен на равные части, для которых есть O(1) индекс.
EP>В C++ такая структура данных есть начиная с первого ISO 1998 — std::deque. Аналог для C# разыскивался в том КСВ топике
Автор: Evgeny.Panasyuk
Дата: 21.10.13
, но никакой вменяемой альтернативы знатоками C# продемонстрировано не было. Поэтому один из вариантов — сделать такой chunked array самому.

Это действительно шикарная штука, которая решила бы все проблемы и даже есть готовая реализация:
http://blogs.msdn.com/b/joshwil/archive/2005/08/10/450202.aspx
Но есть одно "но". Это C#. И если какой-нибудь метод какой-нибудь библиотеки хочет на получить на вход byte[], ему не удастатся подсунуть собственную реализацию. =\

Отличный пример: Encoding.GetChars.
Он принимает либо указатель на ByVal массив, либо массив. Допустим, мы загрузили 200мб текстовых данных и хотим превратить их в единую строку. О том, что это уродство и так делать нельзя, потому что так делать нельзя никогда, на время забудем. По кускам мы её читать не можем — ведь потом всё равно придётся склеивать, клеить будем через StringBuilder, внутри которого лежит всё тот же массив, только уже 2х байтовых char'ов.
И вот как тут быть? Выделять неуправляемую память или использовать управляемые массивы?
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: Pavel Dvorkin Россия  
Дата: 02.02.14 14:06
Оценка:
Здравствуйте, Albeoris, Вы писали:

A>Нельзя заменять динамические массивы на статичные буферы или заменять загрузку всего файла в память на поточную обработку или MemoryMappedFile.


Могу допустить, что почему-то нельзя использовать поточную обработку, но вот почему нельзя использовать MemoryMappedFile — не ясно. Это то же самое выделение памяти, причем неуправляемой памяти, и это выделение полностью подконтрольно программисту, а значит, эту память можно освобождать, когда потребуется. И ИМХО это лучшее решение.
With best regards
Pavel Dvorkin
Re[3]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Evgeny.Panasyuk Россия  
Дата: 02.02.14 14:32
Оценка:
Здравствуйте, Albeoris, Вы писали:

EP>>Если выделять буферы одинакового размера, то внешняя фрагментация не страшна, даже без компактификации, даже на простейших аллокаторах.

A>Увы и ах. К сожалению, это невозможно. Буферы одинакового размера выделять нельзя из-за того, что максимальный размер данных может быть достаточно велик, а количество параллельных запросов — огромно.

Тут я имел в виду именно внутренние буферы. То есть те, которые внутри одного chunked array.

A>Это действительно шикарная штука, которая решила бы все проблемы и даже есть готовая реализация:

A>http://blogs.msdn.com/b/joshwil/archive/2005/08/10/450202.aspx
A>Но есть одно "но". Это C#. И если какой-нибудь метод какой-нибудь библиотеки хочет на получить на вход byte[], ему не удастатся подсунуть собственную реализацию. =\

В C++ было бы тоже самое — если сторонний код ожидает именно плоский кусок памяти, то с std::deque он автоматом не заработает.

A>Отличный пример: Encoding.GetChars.

A>Он принимает либо указатель на ByVal массив, либо массив. Допустим, мы загрузили 200мб текстовых данных и хотим превратить их в единую строку. О том, что это уродство и так делать нельзя, потому что так делать нельзя никогда, на время забудем. По кускам мы её читать не можем — ведь потом всё равно придётся склеивать, клеить будем через StringBuilder, внутри которого лежит всё тот же массив, только уже 2х байтовых char'ов.
A>И вот как тут быть? Выделять неуправляемую память или использовать управляемые массивы?

Допустим есть API принимающее byte[] — как туда передать память выделенную AllocHGlobal (вариант из исходного сообщения)?
Re[4]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 02.02.14 15:22
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Допустим есть API принимающее byte[] — как туда передать память выделенную AllocHGlobal (вариант из исходного сообщения)?
Смотри пример. Счетаем, что у нас есть две перегрузки: одна работает с управляемым массивом, другая с неуправляемым.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 02.02.14 15:25
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Могу допустить, что почему-то нельзя использовать поточную обработку, но вот почему нельзя использовать MemoryMappedFile — не ясно. Это то же самое выделение памяти, причем неуправляемой памяти, и это выделение полностью подконтрольно программисту, а значит, эту память можно освобождать, когда потребуется. И ИМХО это лучшее решение.
Смотри пример в соседнем сообщении. Если какой-либо метод умеет работать исключительно с двумя видами массивов — управляемым и неуправляемым, то ни в том, ни в другом случае ему не подойдет MMF. И я специально подчеркнул, что данные варианты не рассматриваются, вот и не нужно их рассматривать. :D (А в своём решение проблемы я как раз и перевел всё на MMF)
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[5]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Evgeny.Panasyuk Россия  
Дата: 02.02.14 16:00
Оценка:
Здравствуйте, Albeoris, Вы писали:

EP>>Допустим есть API принимающее byte[] — как туда передать память выделенную AllocHGlobal (вариант из исходного сообщения)?

A>Смотри пример. Счетаем, что у нас есть две перегрузки: одна работает с управляемым массивом, другая с неуправляемым.

ОК, значит всё API, куда ты будешь передавать считанные массивы, поддерживает две перегрузки: для Byte[] и для Byte*?
Первый вариант требует хорошего аллокатора/компактификации/x64/явных GC.Collect/etc — и по всей видимости не подходит, так?
Значит нужно так или иначе работать с сырой памятью, обёрнутой во что-то поддерживающее IDisposable. Схема аллокации зависит от требований, паттернов использования — можно использовать тот аллокатор, который предоставляет runtime, а можно сделать свой, более подходящий под задачу.
Re[6]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 02.02.14 16:11
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>ОК, значит всё API, куда ты будешь передавать считанные массивы, поддерживает две перегрузки: для Byte[] и для Byte*?

Ага.

EP>Первый вариант требует хорошего аллокатора/компактификации/x64/явных GC.Collect/etc — и по всей видимости не подходит, так?

Так вот я и пришёл с этим вопросом. Что не так с управляемыми массивами в .NET < 4.5.1. У меня много разрозрненных сведений на этот счёт, но нет ничего конкретного, подкрепленного авторитетным мнение. Тобишь что не так с дефолтным аллокатором, почему отсутствие компактификации может(?) привести к OOME? Связана ли проблема с AnyCPU\x86 или распространяется также на x64? Действительно ли нужно использовать GC.Collect(2) и зачем, если память должна освобождаться автоматически при достижении предела доступной оперативки?
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: mogikanin Россия  
Дата: 02.02.14 16:34
Оценка: -1
Здравствуйте, Albeoris, Вы писали:

A>Приложение использует .NET Framework, сборка AnyCPU.

A>Последовательная обработка файлов, загружает их полностью в память в виде массивов byte[].
A>Размер файлов колеблется от нескольких килобайт до нескольких мегабайт.
A>После N'го файла (>200) приложение падает с OutOfMemoryException.
A>Почему это происходит?

Не надо искать причину там где ее нет. Наверняка был банальный мемори-лик — забывали отписаться от события, не очищали неуправляемые ресурсы, держали массивы от всех файлов в памяти и т.д.
Ибо такой тривиальный кейс не может привести к аутофмемори при наличии прямых рук.
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 02.02.14 16:43
Оценка:
Здравствуйте, mogikanin, Вы писали:

M>Не надо искать причину там где ее нет. Наверняка был банальный мемори-лик — забывали отписаться от события, не очищали неуправляемые ресурсы, держали массивы от всех файлов в памяти и т.д.

M>Ибо такой тривиальный кейс не может привести к аутофмемори при наличии прямых рук.

В том то и дело, что неуправляемые ресурсы не использовались, событий не было, массивы существовали только в локальных переменных. А сейчас стоит вопрос — что всё-таки использовать.
Но в любом случае спасибо. Я пока тоже не могу повторить подобную ситуацию. В самом худьшем случае выжирается гигабайт памяти, после чего она всё равно освобождается. Мистика какая-то.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[7]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Evgeny.Panasyuk Россия  
Дата: 02.02.14 16:47
Оценка:
Здравствуйте, Albeoris, Вы писали:

EP>>Первый вариант требует хорошего аллокатора/компактификации/x64/явных GC.Collect/etc — и по всей видимости не подходит, так?

A>Так вот я и пришёл с этим вопросом. Что не так с управляемыми массивами в .NET < 4.5.1. У меня много разрозрненных сведений на этот счёт, но нет ничего конкретного, подкрепленного авторитетным мнение. Тобишь что не так с дефолтным аллокатором, почему отсутствие компактификации может(?) привести к OOME?

AFAIK, в 4.5.1 компактификацию нужно вызывать вручную. Вот в этом тесте
Автор: Evgeny.Panasyuk
Дата: 17.10.13
, для VS2012, я её не включал — но как видно разница существенная. Также видно, что аллокатор в VS2010 для этого сценария работает лучше чем в предыдущих версиях, но ещё не так хорошо как в VS2012.
Вероятнее всего проблема .NET < 4.5.1 была именно в дубовом аллокаторе, а не в отсутствии компактификации (которую я не включал в тестах выше). Обрати внимание на первую цитату про ".NET prefers to put new memory blocks at the end of the heap".

Если есть интерес — то можешь добавить дополнительный вывод в тот тест, а именно печать тех адресов, которые отдаёт аллокатор, по которым можно построить диаграмму. Это поможет подтвердить или опровергнуть гипотезу про дубовый аллокатор.

A>Связана ли проблема с AnyCPU\x86 или распространяется также на x64?


На x64 доступно намного больше виртуального пространства. Без всяких дополнительных настроек на Windows 7 x64 Prof мне удалось зарезервировать
Автор: Evgeny.Panasyuk
Дата: 30.11.13
1.75TiB по VirtualAlloc. Это примерно на три порядка больше того, что есть в x32. Проблема фрагментации если не решается полностью, то как минимум отодвигается очень далеко (хотя и тут могут быть "фокусы" со стороны аллокатора, например если он сразу съедает всю память, одним большим сплошным куском, и не отдаёт VirtualFree — но это маловероятно).

A>Действительно ли нужно использовать GC.Collect(2) и зачем, если память должна освобождаться автоматически при достижении предела доступной оперативки?


Могу предположить, что это необходимо для того, чтобы помочь аллокатору — чем больше доступной памяти, тем легче ему дышится.
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Evgeny.Panasyuk Россия  
Дата: 02.02.14 16:50
Оценка:
Здравствуйте, mogikanin, Вы писали:

M>Не надо искать причину там где ее нет. Наверняка был банальный мемори-лик — забывали отписаться от события, не очищали неуправляемые ресурсы, держали массивы от всех файлов в памяти и т.д.

M>Ибо такой тривиальный кейс не может привести к аутофмемори при наличии прямых рук.

Можешь попробовать вот этот пример на x32, <4.5.1
Re[3]: Почему GC не освобождает память при выделении больших управляемых массиво
От: mogikanin Россия  
Дата: 02.02.14 17:55
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, mogikanin, Вы писали:


M>>Не надо искать причину там где ее нет. Наверняка был банальный мемори-лик — забывали отписаться от события, не очищали неуправляемые ресурсы, держали массивы от всех файлов в памяти и т.д.

M>>Ибо такой тривиальный кейс не может привести к аутофмемори при наличии прямых рук.

EP>Можешь попробовать вот этот пример на x32, <4.5.1

Попробовал, и что? Никакой связи с проблемой ТС. Хороший пример кривых рук — капасити нужно задавать явно, что и пишется в конце статьи.
Re[4]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Evgeny.Panasyuk Россия  
Дата: 02.02.14 18:36
Оценка:
Здравствуйте, mogikanin, Вы писали:

EP>>Можешь попробовать вот этот пример на x32, <4.5.1

M>Попробовал, и что?

Теперь попробуй на 4.5.1

M>Никакой связи с проблемой ТС. Хороший пример кривых рук — капасити нужно задавать явно, что и пишется в конце статьи.


Пример показывает дубовость аллокатора. На .NET 4.5.1 выделяется 1738MiB (без ручной компактификации), а на VS2005 C++ на аналогичном тесте — 1915MiB.
У ТС аллокация массивов разного размера, которые попадают в LOH. И если у него по смыслу много свободной памяти и вылетает OOM — то очень вероятно что это как раз и есть фрагментация
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: Sharowarsheg  
Дата: 02.02.14 19:59
Оценка: +3
Здравствуйте, Albeoris, Вы писали:

A>У меня есть множество предположений на этот счёт, но они не подкреплены фактами. И главное — я никак не могу вопросизвести ситуацию, в которой получил ошибку. А это очень важно.

A>Если кто-нибудь может дать ответ на этот вопрос — помогите, пожалуйста.

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

A>И связанный с этим вопрос:

A>Если бы у вас возникла необходимость в разработке стабильного и шустрого приложения, которое использует динамические буферы, как в примере выше и не может себе позволить вызывать GC.Collect после обнуления каждого, что бы вы использовали: управляемые массивы или Marshal.AllocHGlobal, обёрнутый в SafeBuffer? Почему? Если не сложно — приведите за и против, которые придут на ум.

Если обрабатываются файлы последовательно, то делается один глобальный массив и он ресайзится в бОльшую сторону, когда нужно. И никогда не освобождается и не ресайзится в меньшую сторону. Если файл меньшего размера, используется начало от массива. Если многопоточность, то один такой буфер на поток (если потоки долго живут) или пул буферов, завернутый в семафор (если потоки часто умирают). Всё остается managed, но при этом нагрузка на аллокатор падает.
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 02.02.14 21:43
Оценка:
Здравствуйте, Albeoris, Вы писали:

A>1) Управляемые массивы больше 85000 байт выделяются в большой куче (LOH),


Не в большой куче, а в куче больших объектов

A>3) Объекты, пережившие несколько сборок мусора и помещенные во второе поколение, в котором могут жить неопредленно долго после утраты всех ссылок.


К объектам, помещаемым в LOH, это не относится. LOH существует вне системы поколений.

A>У меня есть множество предположений на этот счёт, но они не подкреплены фактами. И главное — я никак не могу вопросизвести ситуацию, в которой получил ошибку. А это очень важно.

A>Если кто-нибудь может дать ответ на этот вопрос — помогите, пожалуйста.

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

A>Если бы у вас возникла необходимость в разработке стабильного и шустрого приложения, которое использует динамические буферы, как в примере выше и не может себе позволить вызывать GC.Collect после обнуления каждого, что бы вы использовали: управляемые массивы или Marshal.AllocHGlobal, обёрнутый в SafeBuffer?


Я бы, во-первых, попытался бы отказаться вообще от больших кусков в памяти и работать в модели потоков, если нет, то перейти на х64, а если по каким то причинам и это не прокатывает, то вместо больших кусков хранил бы данные в цепочке маленьких, меньше 85К размером.
... << RSDN@Home 1.2.0 alpha 5 rev. 100 on Windows 8 6.2.9200.0>>
AVK Blog
Re[8]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 03.02.14 01:30
Оценка:
Здравствуйте, Evgeny.Panasyuk,
Огромное спасибо. Это помогло найти ответы на все вопросы.
Был найден даже кэш, который редко чистился и приводил к падению именно на x86-конфиге. Вопрос решен.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 03.02.14 01:32
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>К объектам, помещаемым в LOH, это не относится. LOH существует вне системы поколений.

Буду знать.

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

Угу, только эти куски должны были чиститься. И они чистились, но уже после того, как приложение успевало исчерпать все запасы памяти, доступной для x86.

Проблема решена, спасибо.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 03.02.14 01:34
Оценка:
Здравствуйте, Sharowarsheg, Вы писали:

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

Угу. Вопрос решен.

S>Если обрабатываются файлы последовательно, то делается один глобальный массив и он ресайзится в бОльшую сторону, когда нужно. И никогда не освобождается и не ресайзится в меньшую сторону. Если файл меньшего размера, используется начало от массива. Если многопоточность, то один такой буфер на поток (если потоки долго живут) или пул буферов, завернутый в семафор (если потоки часто умирают). Всё остается managed, но при этом нагрузка на аллокатор падает.

Спасибо!
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: Nikolay_P_I  
Дата: 03.02.14 05:28
Оценка: 9 (1)
Так это давно известная, но малоафишируемая ботва. Я в этой ветке этот эффект еще про FrameWork 1.1 описывал. С тех пор его перманентно костылят, но так как аппетиты разработчиков перманентно же растут — народ перманентно влетает на те же грабли.

Если кратко описать проблему — реальный механизм работы GC не похож на то, что описано в статьях. В одном четком месте — при нехватке памяти GC НЕ начинает СРАЗУ чистить память. GC чистит память по некой эвристике. И иногда банально не успевает. И, в частности, потому вызов GC.Collect — не панацея.

Тестовая программа проста — циклическое выделение массивов все увеличивающегося размера. Причем ссылки на массивы не хранятся и теоретически они собираются GC. На практике же получаем веселые эффекты типа GC.Collect то помогает, то нет, иногда помогает связка GC.Collect + Thread.Sleep(1000), иногда нет. Самый веселый эффект был на FW 1.1, когда массивы средней величины давали OutOfMemory, а большой — нет

Вывод: На разных версиях и разрядностях FW всё по-разному, но работа с частым выделением больших массивов на .NET около границы памяти — опасна.
Re[3]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Sinix  
Дата: 03.02.14 05:29
Оценка:
Здравствуйте, Albeoris, Вы писали:

A>Отличный пример: Encoding.GetChars.

A>И вот как тут быть? Выделять неуправляемую память или использовать управляемые массивы?
Encoding.GetDecoder()?
Re: Почему GC не освобождает память при выделении больших управляемых массивов?
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 03.02.14 07:41
Оценка:
Здравствуйте, Albeoris, Вы писали:

A>Приложение использует .NET Framework, сборка AnyCPU.

A>Последовательная обработка файлов, загружает их полностью в память в виде массивов byte[].
A>Размер файлов колеблется от нескольких килобайт до нескольких мегабайт.
A>После N'го файла (>200) приложение падает с OutOfMemoryException.
A>Почему это происходит?

Пудозреваю, скорее всего тупо заканчивается память из за утечек. Нужен конкретный код.

A>Общеизвестные факты:

A>1) Управляемые массивы больше 85000 байт выделяются в большой куче (LOH), которая до 4.5.1 не поддерживала сжатие (Compact), а данные в ней не могли перемещаться, и частое выделение (и удерживание) массивов приводит к её фрагментации.

Если массивы не удерживаются в памяти, то дотнет поосвобождает все что надо. В x32 ты можешь рассчитывать в среднем на 1гб памяти. Если выделять большими блоками, около 1.5, но надо учитывать, что все сборки, данных, UI и тд так же съедает память.

A>У меня есть множество предположений на этот счёт, но они не подкреплены фактами. И главное — я никак не могу вопросизвести ситуацию, в которой получил ошибку. А это очень важно.

A>Если кто-нибудь может дать ответ на этот вопрос — помогите, пожалуйста.

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

A>И связанный с этим вопрос:

A>Если бы у вас возникла необходимость в разработке стабильного и шустрого приложения, которое использует динамические буферы, как в примере выше и не может себе позволить вызывать GC.Collect после обнуления каждого, что бы вы использовали: управляемые массивы или Marshal.AllocHGlobal, обёрнутый в SafeBuffer? Почему? Если не сложно — приведите за и против, которые придут на ум.

Marshal.AllocHGlobal нужен для исключения влияния GC, то есть, вообще. Это нужно для всяких систем, где важны гарантии времени отклика, т.е. просто адский реалтайм и тд и тд.

Если требований реалтайма нет, то надо по максимуму использовать GC. Самое главное — всеми силами бороться с ростом Gen2. Если можно пересоздать объект заново, то так и стоит делать.
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 03.02.14 07:52
Оценка: -1
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Например, можно использовать не массивы, а так называемые "chunked arrays": буфер поделен на равные части, для которых есть O(1) индекс.

EP>В C++ такая структура данных есть начиная с первого ISO 1998 — std::deque. Аналог для C# разыскивался в том КСВ топике
Автор: Evgeny.Panasyuk
Дата: 21.10.13
, но никакой вменяемой альтернативы знатоками C# продемонстрировано не было. Поэтому один из вариантов — сделать такой chunked array самому.


Без каких либо внятных сведений о наличии-отсутствии конского расхода памяти, утечек, состоянии приложения на момент OOM крайне странно советовать какие то структуры данных.

Что касается chunked array, то ты вопрошал про deque, которая является частным случаем chunked array и собственно в дотнете от неё толку около нуля. Просто варианты chunked array тебе показывали, так шта...
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 03.02.14 07:55
Оценка:
Здравствуйте, AndrewVK, Вы писали:

A>>1) Управляемые массивы больше 85000 байт выделяются в большой куче (LOH),


AVK>Не в большой куче, а в куче больших объектов


Не в куче больших объектов, а в кучах больших объектов
Re[3]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Evgeny.Panasyuk Россия  
Дата: 03.02.14 17:45
Оценка:
Здравствуйте, Ikemefula, Вы писали:

EP>>Если выделять буферы одинакового размера, то внешняя фрагментация не страшна, даже без компактификации, даже на простейших аллокаторах.

EP>>Например, можно использовать не массивы, а так называемые "chunked arrays": буфер поделен на равные части, для которых есть O(1) индекс.
EP>>В C++ такая структура данных есть начиная с первого ISO 1998 — std::deque. Аналог для C# разыскивался в том КСВ топике
Автор: Evgeny.Panasyuk
Дата: 21.10.13
, но никакой вменяемой альтернативы знатоками C# продемонстрировано не было. Поэтому один из вариантов — сделать такой chunked array самому.

I>Без каких либо внятных сведений о наличии-отсутствии конского расхода памяти, утечек, состоянии приложения на момент OOM крайне странно советовать какие то структуры данных.

Я показал один из вариантов что делать при фрагментации. Фрагментация у ТС (а он её явно упоминал), или что-то иное — это другой вопрос.
Ничего странного тут нет

I>Что касается chunked array, то ты вопрошал про deque, которая является частным случаем chunked array и собственно в дотнете от неё толку около нуля. Просто варианты chunked array тебе показывали, так шта...


Дело было так
Автор: Evgeny.Panasyuk
Дата: 18.10.13
:

EP>>>>>О, кстати. Я спрашивал про аналог C++1998 std::deque — мне пытались впарить LinkedList

под видом деки. Может ты покажешь какую деку обычно используют в .NET — ведь должно же быть что-то стандартное, или распространённое, раз с List'ом такой ужас-ужас.
S>>>>С List проблем нет, как уже не раз говорилось. В дотнете из коробки есть только однонаправленная Queue<T>, если нужен имено дек — есть готовые реализации, например http://nitodeque.codeplex.com/
EP>>>Нужен именно O(1) random-access реализованный на чанках, как и std::deque.
EP>>>По этой ссылке, вроде то что нужно — "This deque provides O(1) indexed access,". Но смущает: "all page views=613" — для такой базовой структуры не слишком надёжно.
S>>Если нужно именно на чанках — есть BigList в PowerCollections, больше ничего на глаза не попадалось, специально не искал.
EP>Вот, уже теплее "all page views=296189". Но судя по тому, что там, как они говорят, эффективные вставки в середину — это не прямой аналог std::deque.
EP>Точно: "Getting or setting an item takes time O(log N), where N is the number of items in the list" против O(1) у std::deque. То есть это, по идее, аналог std::map<std::size_t,T>.

Сначала мне пытались продать linked list под видом chunked array, а потом дерево. Нормальный chunked array никто так и не показал
Re[4]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 03.02.14 18:04
Оценка: :)
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Я показал один из вариантов что делать при фрагментации. Фрагментация у ТС (а он её явно упоминал), или что-то иное — это другой вопрос.

EP>Ничего странного тут нет

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

EP>Сначала мне пытались продать linked list под видом chunked array, а потом дерево. Нормальный chunked array никто так и не показал


Доступ по индексу в дотнете никогда не бывает узким местом. Теоретически, возможно, но в большинстве случаев это означает, что задачу надо переписать на С++. То есть, "чтото на чунках" можно слепить как угодно и замедлить доступ по индексу практически до линейной зависимости и это даст только профит. Потому есть всякие BigList, ScanList и очереди всяких сортов, а deque нет.

Есть решения для объектов размером более 2gb, но тут такой фокус, что дотнет совсем недавно выбрался из 2gb АП.
Re[2]: Почему GC не освобождает память при выделении больших управляемых массиво
От: Albeoris  
Дата: 04.02.14 18:05
Оценка:
Здравствуйте, Ikemefula, спасибо, учту.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.