Замеры сжатия изображений: десять образцов в браузере

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

Опубликовано tinyimg. Стенд, генератор образцов и файлы результатов лежат в репозитории, поэтому метод и цифры можно проверить.

Что измерялось и как?

Десять случаев из семи файлов, по пять измерений на случай: бенчмарк измеряет время сжатия и размер результата. Фотография 12 MP кодируется в JPEG, WebP и AVIF, а скриншот 2 MP — в PNG и WebP.

Стенд идёт по публикуемому пути. Он импортирует src/lib/pool.js и src/lib/worker.js — тот же пул и тот же воркер, которые приложение использует для JPEG, PNG, WebP, AVIF и JXL. Для MotionPhoto он импортирует src/lib/motion-photo/pipeline.js — путь, которым приложение идёт для этого формата; MotionPhoto и в продакшене не проходят через пул воркеров. Здесь нет ни компрессора только для бенчмарка, ни второй реализации, которая могла бы разойтись с первой.

Каждый случай выполняется один раз как прогрев, затем пять раз с измерением. Случаи идут последовательно и не конкурируют за пул из четырёх воркеров. В таблице — медиана пяти замеров. p95 считается по ближайшему рангу: при пяти замерах это самый медленный, без интерполяции и без вида, что пять замеров описывают хвост распределения.

Браузер — headless Chrome 155 со свежим временным профилем на каждый запуск; расширения и фоновая сеть отключены. Страница отдаётся с localhost, отсчёт начинается после готовности пула и модулей WASM. Изоляция между источниками намеренно не включается: она заставила бы wasm-feature-detect сообщить о потоках и переключила бы oxipng на многопоточную сборку, а это не тот путь, что в продакшене.

Машина — Apple M4, 10 ядер, 16 ГБ памяти, macOS на arm64. Браузер сообщает о 10 аппаратных потоках; приложение ограничивает пул четырьмя воркерами.

Какие файлы тестировались?

Семь файлов, от JPEG 0,5 MP до фотографии 24 MP, плюс MotionPhoto 12 MP.

ФайлПикселиВходЧто представляет
photo-12mp.jpg4000×30004.92 MBДетализированная фотография
shot-2mp.png1920×10801.36 MBСкриншот интерфейса с резкими краями и полосами вместо текста
flat-1mp.png1000×1000118.1 KBПлоская графика с малым числом деталей
alpha-4mp.png2000×20008.26 MBИзображение RGBA с настоящим альфа-каналом
large-24mp.jpg6000×40009.66 MBФотография 24 MP
tiny-0.5mp.jpg800×600219.6 KBМаленькое изображение; здесь заметна стоимость вызова
motion-12mp.jpgКадр 4000×3000 + видео 6 с 1280×7209.98 MBСинтетический MotionPhoto, собранный писателем из репозитория

Все образцы сгенерированы ffmpeg из встроенных источников. SHA-256 каждого файла записан в bench/fixtures/manifest.json и проверяется до загрузки браузером: изменённый образец — это явная ошибка, а не тихо сдвинутая база.

Каковы результаты?

Десять случаев: отношения от 1,5% до 69,3% и медианы от 42,0 ms до 1 983,2 ms. Отношение — выход ÷ вход; меньше значит меньше. Медиана и p95 — настенное время всего случая, включая обмен с воркером. ms/MP — медиана, делённая на число мегапикселей.

Таблица и оба графика читают один файл, bench/published.json (id запуска, дата, окружение); при расхождении верен файл.

Степень сжатия по случаямГоризонтальная столбчатая диаграмма. Степень сжатия — размер выхода, делённый на размер входа; чем ниже, тем лучше. От 1.5% у shot-2mp-webp до 69.3% у motion-12mp.Степень сжатия по случаям0255075photo-12mp-jpgphoto-12mp-webpphoto-12mp-avifshot-2mp-pngshot-2mp-webpflat-1mp-pngalpha-4mp-aviflarge-24mp-jpgtiny-0.5mp-webpmotion-12mp8.8%6.5%2.4%43.2%1.5%51.7%4.4%8.6%9.3%69.3%выход ÷ вход (%)чем ниже, тем лучше
Минимум: shot-2mp-webp — 1.5% от исходного. Максимум: motion-12mp — 69.3%.
Время кодирования на мегапиксельГоризонтальная столбчатая диаграмма медианного времени кодирования на мегапиксель, в том же порядке случаев, что и диаграмма степени сжатия. От 51.2 мс/МП у large-24mp-jpg до 516.2 мс/МП у flat-1mp-png.Время кодирования на мегапиксель0200400600photo-12mp-jpgphoto-12mp-webpphoto-12mp-avifshot-2mp-pngshot-2mp-webpflat-1mp-pngalpha-4mp-aviflarge-24mp-jpgtiny-0.5mp-webpmotion-12mp56.483.1165.3296.273.1516.2387.951.287.5111.2мс на мегапиксель (медиана 5 запусков)порядок случаев как на диаграмме сжатия
Быстрее всего на мегапиксель: large-24mp-jpg, 51.2 мс/МП. Медленнее всего: flat-1mp-png, 516.2 мс/МП.
СлучайФорматMPВходВыходОтношениеМедианаp95ms/MP
photo-12mp-jpgJPEG → JPEG124.92 MB444.4 KB8.8%677.0733.456.4
photo-12mp-webpJPEG → WebP124.92 MB325.2 KB6.5%997.51021.783.1
photo-12mp-avifJPEG → AVIF124.92 MB121.8 KB2.4%1983.22026.8165.3
shot-2mp-pngPNG → PNG2.071.36 MB600.6 KB43.2%613.1618.1296.2
shot-2mp-webpPNG → WebP2.071.36 MB21.0 KB1.5%151.3156.173.1
flat-1mp-pngPNG → PNG1118.1 KB61.0 KB51.7%516.2562.9516.2
alpha-4mp-avifPNG → AVIF48.26 MB370.1 KB4.4%1551.71626.3387.9
large-24mp-jpgJPEG → JPEG249.66 MB846.1 KB8.6%1228.11231.051.2
tiny-0.5mp-webpJPEG → WebP0.48219.6 KB20.4 KB9.3%42.043.487.5
motion-12mpMotionPhoto129.98 MB6.92 MB69.3%1334.01377.3111.2

Что говорят цифры?

На фотографии 12 MP AVIF в 3,65 раза меньше JPEG и в 2,93 раза медленнее; WebP между ними, а PNG — исключение без потерь с 296,2 ms/MP.

Насколько AVIF меньше JPEG?

Примерно треть: 2,4% против 8,8% на фотографии 12 MP, то есть 121,8 KB против 444,4 KB. WebP дал 325,2 KB. AVIF в 3,65 раза меньше JPEG и в 2,67 раза меньше WebP. Он же и самый медленный: медиана 1 983,2 ms, в 2,93 раза дольше JPEG и в 1,99 раза дольше WebP. На мегапиксель — 165,3 ms против 56,4 у JPEG и 83,1 у WebP.

Весь обмен в одной строке: чуть больше четверти байтов JPEG за тройное время кодирования. На пакете из сотен изображений это время реально; на одной главной картинке может быть неважно.

Насколько WebP меньше и медленнее JPEG?

На 26,8% меньше и в 1,47 раза медленнее: 325,2 KB против 444,4 KB и 997,5 ms против 677,0 ms на том же источнике 12 MP. Против AVIF он в 2,67 раза больше и в 1,99 раза быстрее. Он не лучший ни на одном краю; он середина в обе стороны.

Насколько быстр JPEG?

677,0 ms для фотографии 12 MP (56,4 ms/MP) и 1 228,1 ms для 24 MP (51,2 ms/MP) — самый быстрый из трёх кодеков здесь. Стоимость на мегапиксель почти ровная — так и выглядит быстрый блочный кодек. Его результат больше обоих современных форматов, но по времени он тот, кого нужно обогнать, и на этой машине никто не обогнал.

Почему PNG медленнее всех на мегапиксель?

296,2 ms/MP на скриншоте 2,07 MP — в 4,05 раза больше, чем 73,1 ms/MP у WebP, потому что оптимизация без потерь ищет фильтры и стратегии DEFLATE для каждого пикселя. Скриншот занял 613,1 ms в PNG и 151,3 ms в WebP. Плоский PNG 1 MP оказался хуже на пиксель: 516,2 ms, потому что фиксированная стоимость оптимизатора делится всего на один мегапиксель.

PNG — без потерь. Каждый пиксель сохраняется, а oxipng тратит время на поиск фильтров и стратегий DEFLATE для потока поменьше; поиск — это и есть смысл, и он же стоимость. Кодек с потерями может остановиться при достижении целевого качества. Оптимизатор без потерь должен установить, что удалять больше нечего, а число комбинаций растёт вместе с изображением.

Разница в размере так же пряма: скриншот весил 600,6 KB в PNG и 21,0 KB в WebP. Это не эквивалентные результаты — один без потерь, другой с потерями, — но они объясняют, почему PNG верен для схемы, которую вы будете править, и неверен для фотографии, которую вы публикуете.

Всегда ли более высокий уровень oxipng даёт меньший PNG?

Нет: уровень 3 был на 9,6–13,6% меньше на трёх скриншотах, но сэкономил всего четыре байта на фотографическом PNG 8,29 MP при 8,2-кратном времени. Уровень PNG измерялся отдельно через bench/png-levels.mjs, на тех же образцах, с одним прогревом и пятью прогонами на уровень. На трёх скриншотах (2,07, 8,29 и 15,73 MP) уровень 3 занял в 7,8–8,5 раза больше времени, чем уровень 1, а файлы вышли на 9,6–13,6% меньше. На фотографическом PNG 8,29 MP он дал на четыре байта меньше уровня 1 — 15 474 918 против 15 474 922 — и занял в 8,2 раза больше времени. На изображении RGBA он сэкономил 2,0% и занял в 7,1 раза больше времени. Дополнительный поиск почти ничего не покупает, когда изображение уже фотографическое.

Поэтому публикуемый воркер теперь по умолчанию использует уровень 2 и опускается до уровня 1 выше 4 MP. На 16 MP уровень 2 был на 1,0% больше уровня 1 и примерно вдвое медленнее, так что более высокий уровень не означает автоматически меньший файл. Цена изменения видна на плоских PNG с малым числом деталей: плоский образец 1 MP весит 62 501 B на уровне 2 против 48 138 B на уровне 3 — на 29,8% больше, — но кодируется за 437,9 ms вместо 2 028,3 ms, в 4,6 раза быстрее. Скриншоты платят меньше: при 2,07 MP файл уровня 2 на 7,2% больше уровня 3 и в 4,1 раза быстрее. Сайт выбрал более быстрый вариант по умолчанию и принимает эти байты.

Сжимает ли MotionPhoto видео?

Нет: перекодируется только кадр, а видео копируется байт в байт, поэтому синтетический файл 12 MP уменьшился на 30,7%, хотя половину его не трогали. MotionPhoto — это один файл: JPEG-кадр с добавленным видео и, на некоторых телефонах, gain map. Публикуемый путь декодирует и перекодирует кадр, затем собирает контейнер заново. Оно не декодируется, не перекодируется и не предлагается как цель. Gain map, если он есть, тоже копируется, а его абсолютное смещение в MPF переуказывается, чтобы HDR-показ продолжал работать.

Поэтому отношение для всего файла определяется тем, какую долю занимает видео. В синтетическом образце 12 MP кадр — это 5 156 418 B из 10 463 084 B входа (49,3%), видео — 5 306 666 B (50,7%); gain map нет. Кадр вышел 1 944 569 B, на 62,3% меньше, а весь файл — с 9,98 MB до 6,92 MB, на 30,7% меньше. Экономия кадра велика; экономия файла ограничена той половиной, которую не трогали.

Проверка на реальном образце использует MotionPhoto с OnePlus 13: кадр 7 140 978 B (44,7%), видео 7 968 544 B (49,9%), gain map 848 042 B (5,3%), всего 15 957 564 B. После пути «только кадр»: кадр 1 862 140 B (на 73,9% меньше), видео и gain map побайтно идентичны, всего 10 712 466 B — с 15,96 MB до 10,71 MB, на 32,9% меньше. Пересобранный файл был распознан как MotionPhoto и проигран на телефоне. Это путь gain map, проверенный на реальном железе, а не выведенный из синтетического образца.

Каковы ограничения?

Шесть оговорок, начиная с синтетических образцов и одной машины.

Как воспроизвести эти цифры?

Пятью командами, от npm ci до npm run bench.

npm ci
npm run build
npm run bench:fixtures
npm run bench:build
npm run bench

Запуск пишет JSON в bench/results/ и печатает таблицу выше в stdout. Сравнение уровней PNG — npm run bench:png-levels. Образцы сверяются с bench/fixtures/manifest.json до загрузки браузером; изменённый файл — жёсткая ошибка, а не тихая новая база.

Частые вопросы

Какой формат сжимает сильнее всего?

AVIF: на фотографии 12 MP он дал 121,8 KB, то есть 2,4% входа, против 6,5% у WebP и 8,8% у JPEG.

Какой формат кодирует быстрее всего?

JPEG: 677,0 ms для фотографии 12 MP против 997,5 ms у WebP и 1 983,2 ms у AVIF.

Почему PNG медленнее на мегапиксель?

Потому что он без потерь: oxipng ищет фильтры и стратегии DEFLATE для каждого пикселя. Скриншот 2,07 MP занял 296,2 ms/MP в PNG и 73,1 ms/MP в WebP — в 4,05 раза меньше.

Сжимает ли MotionPhoto видео?

Нет. Видео копируется байт в байт, перекодируется только кадр; в синтетическом образце 12 MP кадр уменьшился на 62,3%, а весь файл — на 30,7%, с 9,98 MB до 6,92 MB.

Что делают уровни 2 и 3 с размером PNG?

Уровень 3 меньше, но гораздо медленнее. Плоский PNG 1 MP весит 62 501 B на уровне 2 против 48 138 B на уровне 3 — на 29,8% больше, — но кодируется за 437,9 ms вместо 2 028,3 ms.

Сколько прогонов на каждый случай?

Один прогрев и пять измеренных прогонов; таблица показывает медиану этих пяти, а p95 — самый медленный замер.

Об этой статье

Опубликовано tinyimg. Стенд, генератор образцов и файлы результатов лежат в репозитории, а сырой JSON этого запуска пишется в bench/results/. Подпись — проект, а не человек: здесь заявляется не авторитет, а то, что метод записан и цифры можно воспроизвести.

← Назад к tinyimg

en · zh-CN · ja · ko · es · fr · ru · ar