Ограничения памяти браузера при обработке файлов
Мой файл весит всего 15 MB. Почему во вкладке закончилась память?
На диске лежит сжатая версия
Всё недоразумение умещается в одно предложение: число, которое вы видите в файловом менеджере, — это размер сжатых данных, а обрабатывать что-либо в сжатом виде невозможно. Чтобы изменить размер изображения, пересжать, конвертировать или проанализировать его, сначала нужно декодировать его в сырые пиксели, а сырые пиксели огромны.
Арифметика простая. Пиксель в памяти обычно занимает четыре байта — красный, зелёный, синий и альфа. Фотография 4000 × 3000 — это двенадцать миллионов пикселей, значит, декодированный растр весит около 48 MB. JPEG, из которого он получен, мог весить 4 MB. Файл вырос в двенадцать раз ещё до того, как началась хоть какая-то работа.
| Размеры | Пиксели | Декодированный растр |
|---|---|---|
| 1920 × 1080 | 2,1 млн | около 8 MB |
| 4000 × 3000 | 12 млн | около 48 MB |
| 6000 × 4000 | 24 млн | около 96 MB |
| 10000 × 10000 | 100 млн | около 400 MB |
И одной копии никогда не хватает
Реальная цепочка обработки держит одновременно несколько копий: сжатые входные байты, декодированный исходный растр, выходной растр в новых размерах, внутренние буферы кодировщика и сжатый результат. Инструмент, который ещё и показывает предпросмотр, держит ещё одну. Для фотографии 4000 × 3000 из примера выше пиковый рабочий набор в две-три сотни мегабайт — совершенно обычное дело, и это для файла, который выглядел как 4 MB.
Растеризация PDF ведёт себя так же, страница за страницей: страница A4, отрисованная при 300 DPI, — это примерно 2480 × 3508 пикселей, около 35 MB в декодированном виде, а инструмент, который рендерит несколько страниц сразу, умножает эту цифру. С видео дело обстоит ещё хуже, потому что декодеру нужно держать в памяти сразу несколько опорных кадров.
Где проходят настоящие потолки
- Бюджет памяти вкладки задаёт браузер, а не сайт, и он меньше объёма памяти, установленной в машине. К тому же он сжимается, когда заняты другие вкладки.
- Модули WebAssembly, собранные под распространённую 32-битную модель памяти, способны адресовать не больше четырёх гибибайт, а браузеры часто разрешают одному экземпляру заметно меньше.
- У отдельных буферов есть собственная предельная длина, и она намного ниже общего объёма памяти, который может занять страница.
- Мобильные операционные системы завершают вкладку, которая слишком разрослась, без предупреждения и без ошибки, которую страница могла бы перехватить.
- Графическая память — отдельный, меньший пул. Canvas, превышающий максимальный размер, допустимый на платформе, просто не создаётся, сколько бы системной памяти ни было свободно.
Ни один из этих пределов не опубликован в виде единого числа, которое можно посмотреть, потому что все они зависят от браузера, его версии, устройства и от того, что ещё запущено. В этом и состоит настоящая трудность: инструмент не может спросить, сколько памяти ему позволено занять.
Почему телефоны — самый жёсткий случай
У телефонов меньше физической памяти, нет подкачки в настольном понимании и есть операционная система, которая агрессивно отбирает память у фоновых процессов. Вкладка браузера становится фоновым процессом в ту секунду, когда вы отвечаете на сообщение. Поэтому мобильные браузеры держат более жёсткие бюджеты и быстрее выгружают вкладку, а выглядит эта выгрузка для вас обычно не как ошибка, а как перезагрузка страницы с потерей вашей работы.
Практическое следствие в том, что задача, которая на ноутбуке доходит до конца, на телефоне с тем же файлом может оказаться невыполнимой. Это не дефект инструмента. Это граница, заданная железом, и честная реакция — сказать об этом до начала, а не рухнуть на полпути.
Как ведёт себя аккуратный инструмент
- Он оценивает рабочий набор по размерам изображения в декодированном виде, ещё ничего не выделив, и отказывается от задачи, которая заведомо не влезет.
- На устройстве со скромными ресурсами он обрабатывает по одному файлу за раз, а не гонит весь пакет параллельно.
- Он передаёт буферы между страницей и своими воркерами, а не копирует их, поэтому большой файл не существует в двух экземплярах.
- Он освобождает каждый результат сразу после записи и отзывает временные URL, которые иначе продолжали бы удерживать данные.
- Там, где формат позволяет, он работает потоком — страница за страницей или кадр за кадром, — вместо того чтобы загружать в память весь документ целиком.
- Он ограничивает число пикселей, которое готов принять, — именно это и не даёт маленькому файлу, разворачивающемуся в огромный Canvas, уронить вкладку.
Что делать, когда задача сорвалась
- Закройте другие вкладки. Они соперничают за тот же бюджет, а браузеры делят его не поровну.
- Обрабатывайте меньше файлов за раз. Очередь из одного файла медленнее и куда вероятнее дойдёт до конца.
- Сначала уменьшите размеры. Вдвое меньшая длинная сторона сокращает рабочий набор вчетверо, а часто именно этого вы и хотели.
- Разделите большой PDF и обработайте части по отдельности.
- Для самых крупных задач переходите на настольную машину. Это не провал локальной обработки, а правильное использование того железа, которое у вас есть.
- Перезапустите браузер, если вкладка открыта уже несколько дней. Долгоживущие вкладки накапливают память, которую освобождает перезагрузка.
Предел любой оценки
Браузеры выдают лишь грубую подсказку о доступной памяти, а некоторые не выдают ничего. Поэтому любая цифра, которую показывает локальный инструмент, — это оценка, построенная на размерах самого файла и консервативной модели цепочки обработки, а не показание, снятое с операционной системы. Она полезна, чтобы решить, стоит ли браться за задачу. Она не обещает, что задача будет доведена до конца, потому что решающий фактор — чем остальное устройство займётся в ближайшие тридцать секунд — веб-странице не виден.
Инструменты для этого
Источники
- W3C — WebAssembly Core Specification, memory model
- W3C — Device Memory API, and why the value is coarse
- WHATWG — HTML Standard, transferable objects