Распаковка GZIP C# OutOfMemory

У меня есть много больших файлов gzip (примерно 10–200 МБ), которые я скачал с ftp для распаковки.

Поэтому я попытался погуглить и найти какое-нибудь решение для распаковки gzip.

    static byte[] Decompress(byte[] gzip)
    {
        using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            const int size = 4096;
            byte[] buffer = new byte[size];
            using (MemoryStream memory = new MemoryStream())
            {
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                }
                while (count > 0);
                return memory.ToArray();
            }
        }
    }

он хорошо работает для любых файлов размером менее 50 МБ, но как только я ввел более 50 МБ, я получил исключение системы из памяти. Последняя позиция и длина памяти перед исключением - 134217728. Я не думаю, что это связано с моей физической памятью, я понимаю, что у меня не может быть объекта больше 2 ГБ, так как я использую 32-разрядную версию.

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

Мои вопросы

  • почему я получил System.OutMemoryException?
  • каково наилучшее решение для распаковки файлов gzip и последующей обработки текста?

person William Calvin    schedule 03.05.2012    source источник
comment
Вы загружаете все содержимое потока в память и возвращаете его в виде массива байтов. Чего еще ожидать, кроме, кроме исключения нехватки памяти? Вы не должны загружать все это в память таким образом — что вы в конечном итоге собираетесь делать с массивом? Записать в файл? Что бы вы ни намеревались, это должно быть основано на потоке, а не на основе массива.   -  person Kirk Woll    schedule 03.05.2012
comment
ну .. Исключение возникает в memory.write и застревает там в 134217728 .. Я не знаком с управлением памятью, поэтому, пожалуйста, потерпите меня. Позже я сохраню все обработанные файлы в базу данных, файл внутри сжатых файлов - это файл csv.   -  person William Calvin    schedule 03.05.2012
comment
Конечно, но ваш дизайн будет лучше, если вы обработаете его во время распаковки. Таким образом, вам не пришлось бы выделять огромный кусок памяти для его обработки. (например, бросив ваш gzip-поток прямо в StreamReader)   -  person Kirk Woll    schedule 03.05.2012
comment
Вероятно, ошибку легче всего заметить в прототипе вашей функции: static byte[] Decompress(byte[] gzip). Вы хотите взять поток в качестве параметра, а не массив.   -  person sarnold    schedule 03.05.2012
comment
Спасибо за предложение. Я попробую использовать поток.   -  person William Calvin    schedule 03.05.2012
comment
любое окончательное решение с полным образцом исходного кода?   -  person Kiquenet    schedule 13.12.2012


Ответы (4)


Стратегия выделения памяти для MemoryStream не подходит для огромных объемов данных.

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

  • длинные задержки копирования при перераспределении
  • новый массив должен соответствовать свободному адресному пространству, уже сильно фрагментированному предыдущими выделениями
  • новый массив будет в куче LOH, что имеет свои особенности (без уплотнения, сборка на GC2).

В результате обработка большого (100 МБ+) потока через MemoryStream, скорее всего, приведет к исключению нехватки памяти в системах x86. Кроме того, наиболее распространенным шаблоном для возврата данных является вызов GetArray, как вы это делаете, что дополнительно требует примерно того же объема пространства, что и последний буфер массива, используемый для MemoryStream.

Подходы к решению:

  • Самый дешевый способ — предварительно увеличить MemoryStream до необходимого вам размера (желательно чуть больше). Вы можете предварительно вычислить размер, который требуется, читая поддельный поток, который ничего не хранит (пустая трата ресурсов ЦП, но вы сможете его прочитать). Также рассмотрите возможность возврата потока вместо массива байтов (или возврата массива байтов буфера MemoryStream вместе с длиной).
  • Другой способ справиться с этим, если вам нужен весь поток или массив байтов, — использовать поток временных файлов вместо MemoryStream для хранения большого объема данных.
  • Более сложный подход заключается в реализации потока, который разбивает базовые данные на блоки меньшего размера (например, по 64 КБ), чтобы избежать выделения в LOH и копирования данных, когда потоку необходимо увеличиться.
person Alexei Levenkov    schedule 03.05.2012
comment
Да, спасибо, что разъяснили мне это. Теперь я вроде понимаю, поток памяти не был для меня хорошим другом в этом случае. Я думал, что это может помочь повысить производительность, но вместо этого у меня больше головной боли. Спасибо - person William Calvin; 03.05.2012

Вы можете попробовать следующий тест, чтобы понять, сколько вы можете записать в MemoryStream, прежде чем получите OutOfMemoryException :

        const int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];

        int fileSize = 1000 * 1024 * 1024;

        int total = 0;

        try
        {
            using (MemoryStream memory = new MemoryStream())
            {
                while (total < fileSize)
                {
                    memory.Write(buffer, 0, bufferSize);
                    total += bufferSize;
                }

            }

            MessageBox.Show("No errors"); 

        }
        catch (OutOfMemoryException)
        {
            MessageBox.Show("OutOfMemory around size : " + (total / (1024m * 1024.0m)) + "MB" ); 
        }

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

Побочный момент: интересно, что на ПК с Windows XP приведенный выше код дает: «OutOfMemory размером около 256 МБ», когда код нацелен на .net 2.0, и «OutOfMemory размером около 512 МБ» на .net 4.

person Moe Sisko    schedule 03.05.2012
comment
Я уже указал выше. Если я прав, он застрял на 134217728 примерно около 128 МБ. Я не уверен, почему это происходит слишком рано, но я думаю, что выбор потока памяти - моя первая ошибка. Спасибо за ваш ответ. - person William Calvin; 03.05.2012
comment
Могу подтвердить, что я достиг ТОЧНО того же предела. - person Kris; 11.01.2017

Вы случайно не обрабатываете файлы в несколько потоков? Это потребовало бы большого количества вашего адресного пространства. Ошибки OutOfMemory обычно не связаны с физической памятью, поэтому MemoryStream может закончиться намного раньше, чем вы ожидаете. Проверьте это обсуждение http://social.msdn.microsoft.com/Forums/en-AU/csharpgeneral/thread/1af59645-cdef-46a9-9eb1-616661babf90. Если вы переключитесь на 64-битный процесс, вы, вероятно, будете более чем в порядке для размеров файлов, с которыми имеете дело.

Однако в вашей текущей ситуации вы можете работать с файлами с отображением памяти, чтобы обойти любые ограничения на размер адреса. Если вы используете .NET 4.0, он предоставляет собственную оболочку для функций Windows http://msdn.microsoft.com/en-us/library/dd267535.aspx.

person Michael Yoon    schedule 03.05.2012
comment
Да, я видел эту ссылку, прежде чем спросить в SO. Я просто хочу знать, какие еще варианты у меня есть. Спасибо за ответ - person William Calvin; 03.05.2012

Я понимаю, что у меня не может быть объекта больше 2 ГБ, так как я использую 32-разрядную версию.

Это неправильно. Вы можете иметь столько памяти, сколько вам нужно. 32-битное ограничение означает, что вы можете иметь только 4 ГБ (ОС занимает половину) виртуального адресного пространства. Виртуальное адресное пространство — это не память. Вот приятно читать.

почему я получил System.OutMemoryException?

Потому что аллокатор не смог найти непрерывное адресное пространство для вашего объекта или это происходит слишком быстро и он забивается. (скорее всего первое)

каково наилучшее решение для распаковки файлов gzip и последующей обработки текста?

Напишите скрипт, который загружает файлы, затем использует такие инструменты, как gzip или 7zip, для их распаковки и последующей обработки. В зависимости от типа обработки, количества файлов и общего размера вам придется сохранить их в какой-то момент, чтобы избежать подобных проблем с памятью. Сохраните их после распаковки и обработайте сразу 1 МБ.

person Lukasz Madon    schedule 03.05.2012
comment
ОП прав в отношении Ограничение размера массива 2 ГБ. Кроме того, я думаю, что предложение внешнего инструмента, такого как 7-zip, полностью не соответствует духу этого вопроса. - person Kirk Woll; 03.05.2012