Распаковка LZMA SDK для iOS (xcode) с использованием слишком большого объема оперативной памяти

Я пытаюсь использовать LZMA SDK в приложении для iPhone/iPad, моей отправной точкой был пример проекта LZMA для iPhone, предоставленный Mo Dejong, доступный здесь: https://github.com/jk/lzmaSDK Оригинал был здесь: http://www.modejong.com/iOS/lzmaSDK.zip (я попробовал оба и получил одинаковый результат от обоих).

Проблема в том, что извлечение использует столько оперативной памяти, сколько .7z содержит в несжатом виде. Другими словами, скажем, у меня есть сжатый файл размером 40 МБ, несжатый файл представляет собой двоичную базу данных sqlite размером около 250 МБ, он будет постепенно использовать все больше и больше памяти, поскольку распаковывает файл до 250 МБ. Это приведет к сбою iPad1 или чего-либо еще до iPhone4 (256 МБ ОЗУ). У меня есть ощущение, что многие люди в конечном итоге столкнутся с этой же проблемой, поэтому решение сейчас может помочь многим разработчикам.

Первоначально я создал файл .7z на ПК, используя Windows 7-zip (последняя версия) и размер словаря 16 МБ. Для распаковки требуется всего 18 МБ ОЗУ (и это тот случай, когда тестируется на ПК с просмотром диспетчера задач). Я также пытался создать архив с помощью keka (архиватора Mac с открытым исходным кодом), это ничего не решило, хотя я могу подтвердить, что сама keka использует только 19 МБ оперативной памяти во время извлечения файла на Mac, чего я и ожидал. Думаю, следующим шагом будет сравнение исходного кода Keka с исходным кодом LZMA SDK.

Я экспериментировал с разными размерами словаря и другими настройками при создании файла .7z, но ничего не помогло. Я также попытался разбить свой единственный двоичный файл на 24 меньших части перед сжатием, но это также не помогло (по-прежнему используется более 250 МБ ОЗУ для извлечения 24 частей).

Обратите внимание, что ЕДИНСТВЕННОЕ изменение, которое я внес в исходный код, заключалось в использовании файла .7z большего размера. Также обратите внимание, что он сразу же освобождает оперативную память, как только извлечение завершено, но это не помогает. Я чувствую, что он не освобождает ОЗУ, как он извлекает, как должен, или помещает все содержимое в ОЗУ до самого конца, когда это делается, и только затем перемещает его из ОЗУ. Кроме того, если я попытаюсь извлечь тот же самый файл с помощью приложения для Mac во время работы с инструментами, я не увижу того же поведения (например, StuffIt Expander использует около 60 МБ ОЗУ при извлечении файла, Keka, Mac с открытым исходным кодом). архиватор исчерпал 19 МБ ОЗУ).

Я не очень разбираюсь в mac/xcode/objective-c (пока), поэтому любая помощь в этом будет очень признательна. Вместо этого я мог бы прибегнуть к использованию zip или rar, но с LZMA я получаю намного лучшее сжатие, поэтому, если это вообще возможно, я хочу придерживаться этого решения, но, очевидно, мне нужно, чтобы оно работало без сбоев.

Спасибо!

Снимок экрана: Instruments.app профилирует пример приложения


person tradergordo    schedule 25.09.2012    source источник
comment
Извини, Дарен, до сих пор у меня не было времени, чтобы изучить это глубже. Надеюсь, вечером найду время, но ничего не обещаю.   -  person Jens Kohl    schedule 25.09.2012
comment
Просто для справки, но актуальный URL-адрес github: github.com/mdejong/lzmaSDK.   -  person MoDJ    schedule 03.07.2015


Ответы (3)


Игорь Павлов, автор 7zip, написал мне по электронной почте, он в основном сказал, что наблюдения, которые я сделал в исходном вопросе, являются известным ограничением версии c SDK. Версия C++ не имеет этого ограничения. Фактическая цитата:

«7-Zip использует другой многопоточный декодер, написанный на C++. Этому декодеру C++ .7z не нужно выделять блок ОЗУ для всего сплошного блока. Прочтите также эту тему:

http://sourceforge.net/projects/sevenzip/forums/forum/45797/topic/5655623 "

Так что, пока кто-то не исправит SDK для iOS, обходным путем будет:

1) Решите, какой лимит ОЗУ вы хотите иметь для операций распаковки файлов.

2) Любой ОДИН файл в вашем архиве, который превышает лимит из 1 выше, должен быть разделен, вы можете сделать это с помощью любого приложения для разделения двоичных файлов, такого как splits: http://www.fourmilab.ch/splits/

3) После того, как ваши файлы будут готовы, создайте файл 7z, используя параметры словаря/размера блока, как описано MoDJ в его ответе, например, с ограничением в 24 мегабайта: 7za a -mx=9 -md=24m -ms=24m CompressedFile. Исходные файлы 7z*

4) В приложении для iOS после распаковки файлов определите, какие файлы были разделены, и снова соедините их вместе. Код для этого не так уж и сложен (я предполагаю соглашение об именах, которое использует split.exe, а именно файл.001, файл.002 и т. д.).

    if(iParts>1)
    {
        //If this is a multipart binary split file, we must combine all of the parts before we can use it
        NSString *finalfilePath = whateveryourfinaldestinationfilenameis
        NSString *splitfilePath = [finalfilePath stringByAppendingString:@".001"];

        NSFileHandle *myHandle;
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError *error;

        //If the target combined file exists already, remove it
        if ([fileManager fileExistsAtPath:finalfilePath]) 
        {
            BOOL success = [fileManager removeItemAtPath:finalfilePath error:&error];
            if (!success) NSLog(@"Error: %@", [error localizedDescription]);
        }

        myHandle  = [NSFileHandle fileHandleForUpdatingAtPath:splitfilePath];
        NSString *nextPart;
        //Concatenate each piece in order
        for (int i=2; i<=iParts; i++) {
            //Assumes fewer than 100 pieces
            if (i<10) nextPart = [splitfilePath stringByReplacingOccurrencesOfString:@".001" withString:[NSString stringWithFormat:@".00%d", i]];
            else nextPart = [splitfilePath stringByReplacingOccurrencesOfString:@".001" withString:[NSString stringWithFormat:@".0%d", i]];
            NSData *datapart = [[NSData alloc] initWithContentsOfFile:(NSString *)nextPart];
            [myHandle seekToEndOfFile];
            [myHandle writeData:datapart];
        }    
        [myHandle closeFile];
        //Rename concatenated file
        [fileManager moveItemAtPath:splitfilePath toPath:finalfilePath error:&error];
    }
person tradergordo    schedule 01.10.2012
comment
Я попытался взглянуть на исходный код C++, но, похоже, это код только для Windows. У меня есть некоторые очень простые вещи для компиляции под xcode, но версия C++ не кажется хорошей отправной точкой, потому что она привязана к Windows API. Версия C, по крайней мере, компилируется и работает под UNIX/iOS, даже если она выделяет слишком много памяти. - person MoDJ; 26.01.2013

Хорошо, так что это сложный вопрос. Причина, по которой вы сталкиваетесь с проблемами, заключается в том, что iOS не имеет виртуальной памяти, в отличие от вашей настольной системы. Библиотека lzmaSDK написана таким образом, что предполагается, что в вашей системе достаточно виртуальной памяти для распаковки. Вы не увидите проблем с запуском на рабочем столе. Только при выделении больших объемов памяти для распаковки на iOS вы столкнетесь с проблемами. Было бы лучше решить эту проблему, переписав lzma SDK, чтобы он лучше напрямую использовал отображаемую память, но это нетривиальная задача. Вот как обойти эту проблему.

Использование 7za

На самом деле есть 2 параметра командной строки, которые вы захотите передать программе архивации 7zip, чтобы сегментировать файлы на более мелкие фрагменты. Я собираюсь предложить вам просто использовать размер 24 мегабайта, который я в конечном итоге использовал, поскольку это был достойный компромисс между пространством и памятью. Вот командная строка, которая должна помочь, обратите внимание, что в этом примере у меня есть большие файлы фильмов с именем XYZ.flat, и я хочу сжать их вместе в файл archive.7z:

7za a -mx=9 -md=24m -ms=24m Animations_9_24m_NOTSOLID.7z *.flat

Если вы сравните этот сегментированный файл с версией, которая не разбивает файл на сегменты, вы увидите, что файл становится немного больше при сегментации:

$ ls -la Animations_9_24m.7z Animations_9_24m_NOTSOLID.7z
-rw-r--r--  1 mo  staff  8743171 Sep 30 03:01 Animations_9_24m.7z
-rw-r--r--  1 mo  staff  9515686 Sep 30 03:21 Animations_9_24m_NOTSOLID.7z

Таким образом, сегментация уменьшает сжатие примерно на 800 КБ, но это не такая уж большая потеря, потому что теперь процедуры распаковки не будут пытаться выделить кучу памяти. Использование памяти для декомпрессии теперь ограничено 24-мегабайтным блоком, который может обрабатывать iOS.

Дважды проверьте свои результаты, распечатав информацию заголовка сжатого файла:

$ 7za l -slt Animations_9_24m_NOTSOLID.7z

Path = Animations_9_24m_NOTSOLID.7z
Type = 7z
Method = LZMA
Solid = +
Blocks = 7
Physical Size = 9515686
Headers Size = 1714

Обратите внимание на элемент «Блоки» в приведенном выше выводе, он указывает, что данные были сегментированы на разные блоки по 24 мегабайта.

Если вы сравните приведенную выше информацию о сегментированном файле с выводом без аргумента -ms=24m, вы увидите:

$ 7za l -slt Animations_9_24m.7z

Path = Animations_9_24m.7z
Type = 7z
Method = LZMA
Solid = +
Blocks = 1
Physical Size = 8743171
Headers Size = 1683

Обратите внимание на значение «Блоки», вам не нужен только 1 огромный блок, поскольку он будет пытаться выделить огромный объем памяти при распаковке на iOS.

person MoDJ    schedule 30.09.2012
comment
Я собираюсь продолжать играть с этим, но независимо от того, что я пытаюсь включить точные параметры, которые вы упомянули, я не могу заставить 7za создавать файл .7z с более чем одним блоком. Возможно ли это, потому что я сжимаю только один файл? (вы упомянули в своем сценарии, что у вас есть несколько файлов) - person tradergordo; 01.10.2012
comment
Я почти уверен, что ваш обходной путь будет работать только в том случае, если у вас есть несколько файлов, каждый из которых меньше любого объема оперативной памяти, который вы хотите максимально использовать. Например, если у меня есть 2 500 мегабайт файлов, он создаст 2 блока и приведет к сбою любого iphone/ipad. Чуть более сумасшедшим обходным решением было бы использовать бинарное приложение-разделитель для вашего файла, скажем, у вас есть один файл размером 250 МБ, бинарно разделить его на 10 равных частей, теперь вы получите 10 блоков, и распаковка будет работать на iOS, но я понятия не имею как объединить файлы обратно в один файл в Objective-C. Если у кого-то есть дополнительные идеи, пожалуйста, дайте мне знать. - person tradergordo; 01.10.2012
comment
Итак, я сел и решил настоящую проблему в SDK, и теперь он использует mmap() для извлечения файлов размером примерно до 650 мегабайт без сбоев из-за памяти на iOS. Код доступен в этом репозитории git: github.com/mdejong/lzmaSDK. - person MoDJ; 22.01.2013
comment
Звучит неплохо. Итак, какой объем оперативной памяти используется во время распаковки с вашим решением, если, например, вы имели дело с файлом размером 500 МБ? Почему существует ограничение в 650 МБ? Я использовал метод разделения/объединения двоичных файлов, который отлично работает, но не самый элегантный. - person tradergordo; 22.01.2013
comment
iOS налагает ограничение примерно в 690 мегабайт общей виртуальной памяти, которая может быть отображена на устройстве в любой момент времени. Если вы попытаетесь отобразить 700 мегабайт, вызов mmap завершится ошибкой. Использование этой новой логики mmap означает, что если вы установите верхний предел размера блока в 650, то можно будет декодировать отдельные файлы размером до 650 мегабайт. Попробуйте приведенный выше пример проекта на github, он показывает, как можно создать очень большой архив, один из примеров показывает два файла по 650 мегабайт, которые можно декодировать по одному. Вы не можете декодировать файл размером 1 гиг, но 650 мегабайт на файл — это огромно. Плюс улучшена компрессия. - person MoDJ; 24.01.2013

Я столкнулся с той же проблемой, но нашел гораздо более практичное решение:

  • используйте CPP-интерфейс LZMA SDK. Он использует очень мало памяти и не страдает от проблемы с потреблением памяти, как интерфейс C (как уже правильно сказал tradergordo).

  • взгляните на LZMAAlone.cpp, удалите из него все ненужное (например, кодирование, файлы формата 7-zip и, кстати, кодирование также потребует большой памяти) и создайте крошечный файл заголовка для вашего декомпрессора CPP LZMA, например:

extern "C" int extractLZMAFile (const char *filePath, const char *outPath);

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

  • поскольку у меня нет полной поддержки 7Z, я использую tar в качестве контейнера вместе со сжатыми файлами lzma. На https://github.com/mhausherr/Light-Untar-for-iOS

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

person benjist    schedule 02.03.2013