Pull to refresh

Comments 34

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

Я один раз читаю cжатый файл. Затем распаковываю архив. Это и есть пик, когда загружены и архив и его распакованная версия. Далее сжатый файл удаляется из памяти, во время работы приложения занято памяти ровно на распакованные tar-данные. Пример: data.tar 1000kb, data.tar.gz 250kb. Итого на диске хранится 250kb, пиковое потребление памяти 1250kb, потребление памяти в рабочем режиме — 1000kb. Другое дело, что если tar-файл будет большим, то грузить его весь в оперативку не рационально. Для таких случаев лучше присмотреть другое решение, а не dxTarRead.

Хорошо, я понял вас, и я с вами согласен — но вы не уловили мой момент. Допустим, у нас в архиве хранятся текстуры. Эти текстуры ведь не представляют для нас интереса в виде блобов — с ними нужно что-то делать, верно? Загружать в тот же OpenGL, например. Ну или, скажем, не текстуры, а JSON-строки, которые нужно распарсить и превратить в дерево объектов. Итого, выходит, хронология использования памяти за время жизни приложения:

0MiB — запустились
+40 MiB — прочитали сжатый файл (итого 40 MiB)
+1000 MiB — распаковали сжатый файл в память (итого 1040 MiB)
-40 MiB — выгрузили сжатый файл (итого 1000 MiB)
+200 MiB — загрузили текстуру 1/5 из файла (итого 1200 MiB)
+200 MiB — загрузили текстуру 2/5 из файла (итого 1400 MiB)
+200 MiB — загрузили текстуру 3/5 из файла (итого 1600 MiB)
+200 MiB — загрузили текстуру 4/5 из файла (итого 1800 MiB)
+200 MiB — загрузили текстуру 5/5 из файла (итого 2000 MiB)
-1000 MiB — выгрузили распакованный архив (итого 1000 MiB)

Пиковая загрузка, выходит, 2 GiB, рабочий режим — 1 GiB. Если бы библиотека позволяла итерироваться по файлу, то загрузка была бы следующей:

0MiB — запустились
+240 MiB — распаковали одну текстуру и скопировали в память (итого 240 MiB)
+200 MiB — загрузили текстуру 1/5 из памяти (итого 440 MiB)
-240 MiB — освободили память из-под текстуры и контекста распаковки (итого 200 MiB)
+240 MiB — распаковали одну текстуру и скопировали в память (итого 440 MiB)
+200 MiB — загрузили текстуру 2/5 из памяти (итого 640 MiB)
-240 MiB — освободили память из-под текстуры и контекста распаковки (итого 400 MiB)
+240 MiB — распаковали одну текстуру и скопировали в память (итого 640 MiB)
+200 MiB — загрузили текстуру 3/5 из памяти (итого 840 MiB)
-240 MiB — освободили память из-под текстуры и контекста распаковки (итого 600 MiB)
+240 MiB — распаковали одну текстуру и скопировали в память (итого 840 MiB)
+200 MiB — загрузили текстуру 4/5 из памяти (итого 1040 MiB)
-240 MiB — освободили память из-под текстуры и контекста распаковки (итого 800 MiB)
+240 MiB — распаковали одну текстуру и скопировали в память (итого 1040 MiB)
+200 MiB — загрузили текстуру 5/5 из памяти (итого 1240 MiB)
-240 MiB — освободили память из-под текстуры и контекста распаковки (итого 1000 MiB)

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

Да, здесь согласен. Получается, что у меня в оперативке хранятся лишние картинки в виде сырых данных. Так получилось, что JSON-строки сразу конвертирую в дерево объектов, а память после tar и gzip освобождаю. Но это только из-за того, что JSON нужен весь и сразу. Попробую в своём проекте переделать логику менеджера спрайтов, чтобы сразу при инициализации грузил все картинки в формат текстуры, тогда tar-архив можно будет сразу освободить.

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

Ниже отписали, что отображение в память будет проблемно использовать с компрессором. Мне же нужно сначала распаковать gzip, а потом уже из результата читать tar.

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

Если честно, не особо. Tar мне был просто интересен для исследования. А готовое решение надеялся что в комментах на хабре подскажут толковое.

Погодите-погодите, а зачем считывать архив в память целиком? Почему бы не использовать mmap? (это если не менять предложенный автором код, а вообще в данном случае лучше читать файл блоками фиксированного размера)

Если бы речь шла о .tar-файле на диске — никаких проблем, mmap туда просится естественным образом. Но как натравить mmap на результат распаковки какого-нибудь gzip?

Ну mmap тут, естественно, в пролете. Но ведь gzip это потоковый компрессор, можно разжимать файл по кусочкам и распаковывать tar. Тоже не нужно читать файл в память целиком, но да, это уже не 26 строчек.

Да, и ко всему вышеуказанному — реализация автора статьи однопроходная. Ты указываешь имя файла, которое нужно извлечь, а оно ищет его и извлекает. Это хороший подход, когда весь архив уже в памяти, но если его нужно для каждого файла переизвлекать из gzip, то лучше переписать эту часть логики на "встретил файл — спросил коллбэк, что с ним делать".

Так есть же zlib. Зачем внешнюю программу дёргать, если уж так на всём хочется экономить?

Ну так никто и не предлагает дергать внешнюю компанию — под "gzip" имеется в виду формат архива, а не исполняемый файл, оный архив извлекающий.

tar — не совсем архив в широко распространённом смысле этого слова. Данные в нём не сжимаются, а хранятся в открытом виде

Ну архив сам по себе не подразумевает обязательное сжатие — сжатием архива занимается компрессор. А задача архиватора — получить архив — то есть множество файлов упаковать в один

Именно! Это и пытался описать. Лично меня когда-то очень удивило, что архив — это не обязательно сжатие. Попытаюсь как-нибудь перефразировать, спасибо за замечание.

Боюсь, придётся много чего перефразировать. Rar, zip, gzip — компрессоры. Архиватор в этой статье пока один — tar. У архива задача не просто собрать кучу файлов в один, а ещё и сохранить у этих файлов атрибуты доступа, флаг исполнения и прочие интересные вещи.

А заголовок tar ограничен 512-ю байтами или может быть больше?
Например, как там убирается очень длинный путь к файлу?

По стандарту GNU tar — 512 байт ровно. Очень длинный путь внутри tar не учитывается. Точнее, если будет больше 100 символов — то всё сломается. Если где-то такой вариант и учли в реализации, я об этом не в курсе.

По идее форматов там 14несколько. В довесок есть всякие особенности представления ссылок, расширенных атрибутов, sparse файлов и т.п. Если поставить себе задачу наступить на грабли при чтении произвольного tar архива — это сделать совсем не сложно)
Правильно-ли я понимаю, что основная задача это архивирование? Возможно, для ваших кейсов больше подойдет обычный ar:
ar -cq out.ar file1.txt file2.txt

tar оптимизирован для бэкапов блочных устройств на летны (tape). Отсюда блоки по 512 байт, что соответствует популярному размеру сектора дисков. Но если основной кейс это распаковка в память, то фича скорее не нужна.

Спасибо! Вроде бы формат не сильно отличается от tar. Позже попробую добавить и этот формат в свой "архиватор" на гитхаб.

Покрутил немножко. Я не вижу поддержки директорий, а без них совсем грустно.


И длина имени файла всего 16 символов.

Зачем в файле с ресурсами для игры директории и имена файлов, кто там на них будет смотреть? Blizzard в своем известном формате заменяет все это многобуквенное великолепие просто на хеши (логика разработчиков вполне понятна, хоть и бесит чисто по-человечески). ^)

Чтобы избежать хэшей и редактировать эти ресурсы напрямую? В JSON у меня ссылки вида "effects/snow.png", "sprites/girl.png". Загрузчик смотрит файлы в открытом виде, если нет, то пытается грузить из архива. В котором лежат по этому же пути.

И мне больше понравился формат Cpio. В бинарной версии формата заголовок всего 26 байт + название файла. Реализация также на GitHub.

P.S. Хаб "Я пиарюсь!" я видел. А где хаб "Я ищу работу"? :)

На верху страницы ссылка: Мой круг

Первая четверть XXI века близилась к завершению… С89 всё ещё подавался как преимущество.

А почему не преимущество? Да, в таком стандарте не очень комфортно писать. Но C89 почти гарантирует, что проект успешно скомпилируется в любом современном компиляторе, начиная от Clang и заканчивая восьмибитными микроконтроллерами. Писать свой проект можно на чём угодно, хоть на "Super-mega-boosted-python-java#". Но если есть желание использовать чужую библиотеку, и при этом она написана на C89, то есть приличная вероятность, что она без проблем подключится.
Кроме того, Си есть Си. Т.е. отличная производительность за счёт низкоуровневости языка.

Есть ведь новые стандарты языка.

Есть. Но какова вероятность, что компилятор для микроволновки будет поддерживать свежие стандарты? Или какие-нибудь KolibriOS/MenuetOS? В которых основным компилятором TCC, да ещё и лохматых годов выпуска, да ещё и сурово допиленный напильником. А С89 будет работать везде. Потому, что стандарт относительно простой, и при создании компиляторов Си в первую очередь стремятся к нему.

Получается, можно использовать tar как файловую систему для embedded-устройств!

Но есть варианты и получше
начиная с CRAMfs заканчивая Squashfs
Sign up to leave a comment.

Articles