Конвертация изображений: почему Rust в 8.5× быстрее bash на той же libwebp

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

Открытие

Bash + ImageMagick конвертирует 50 JPG в WebP за 10.09 секунды — через ~100 процессов (один identify на файл, один magick на файл, плюс шеллы для grep/sort/rm). Rust-бинарь делает ту же работу за 1.18 секунды — через 1 процесс с параллелизацией по ядрам. Это не 8.5× магия, это отсутствие накладных расходов на запуск ~99 процессов.

fast-image-converter — это автономный Rust CLI для конвертации изображений между jpg, png и webp. Заменяет bash + ImageMagick там, где 100 процессов на 50 файлов перестали быть незаметными, и не делает вид, что решает задачи вне своей зоны — анимацию, ICC-трансформации, сетевые источники.

Возможности

Шесть пайплайнов — все комбинации между тремя форматами. По умолчанию (без флагов) работает jpg → webp.

Пайплайн | Quality учитывается
jpg → webp (по умолчанию) | да
png → webp | да
webp → jpg | да
png → jpg | да
webp → png (без потерь) | нет
jpg → png (без потерь) | нет

Контроль качества — --quality <1..100> для lossy-пайплайнов (по умолчанию 85). Lossless-пайплайны игнорируют.

Политики изменения размера — --resize принимает четыре формы:

Форма | Что делает
none | без изменения размера
cap=<W> | масштабирует более широкую сторону до ≤ <W> px, сохраняя соотношение сторон
auto:portrait=<W>,landscape=<H> | лимиты по ориентации (v0 baseline: 800/1000)
fit=<mode> long-edge=<N> | contain / cover / stretch под заданный размер

Два режима работы:

Режим | Что делает
Batch | Берёт директорию, конвертирует все файлы в ней. По умолчанию удаляет исходники после успешной конверсии; флаг --keep-source оставляет
Single-file | Читает один файл из stdin, пишет результат в stdout — для встраивания в другие пайплайны

Минимальный пример:

# partial — фрагмент CLI-инвокации, runnable на собранном бинаре.
cat input.jpg | ./target/release/fast-image-converter \
--single-file --output-format webp \
> output.webp

Бинарь — один автономный файл, ~2.4 MiB stripped, без сетевых вызовов, без телеметрии, без внешних сервисов. Линкуется с libwebp напрямую и со статической MozJPEG, чтобы deploy не зависел от системных библиотек.

Совпадение выхода — закодированные байты совпадают с эталонным энкодером libwebp до 0.1% допуска. Это обеспечивается golden-batch регрессионным тестом в репозитории (tests/golden_v0.rs), не «примерно одинаково».

Полный список флагов — --help. Полный контракт (коды выхода, каналы, граничные случаи) — в docs/integration-contract.md репозитория.

Производительность

50 файлов / 3 МБ JPG смешанной ориентации на 12-ядерном хосте с libwebp 1.6.0:

Пайплайн | bash + ImageMagick | fast-image-converter | Ускорение
jpg → webp (по умолчанию) | 10.09 s | 1.18 s | 8.5×

Выходные байты совпадают с эталонным энкодером libwebp до 0.1% допуска — обеспечивается golden-batch регрессионным тестом (tests/golden_v0.rs), не «примерно одинаково».

От чего зависят цифры:

Количество ядер — rayon-пул масштабируется. На 4 ядрах ускорение будет меньше, на 32 — больше.

Версия libwebp — различия в энкодере. Золотая регрессия записана на libwebp 1.6.0; на другой версии дрейф возможен.

Диск — cold cache vs page cache.

Размер и ориентация файлов — landscape-кодирование дольше portrait.

От чего не зависят: накладные расходы на запуск процессов. Их больше нет в принципе — это и есть основная причина ускорения.

Перемеряйте на своём хосте:

time ./target/release/fast-image-converter /path/to/your/jpg-batch

На 500 файлах и 10к файлах цифры будут другие, но порядок сохранится: один процесс с параллелизацией выигрывает у цепочки identify + magick на каждом файле.

Где попробовать

Демо — image-converter.elrise.io: тот же бинарь под веб-обёрткой. Можно пощупать пайплайны и resize без сборки.

Репозиторий — github.com/elriseio/fast-image-converter.

Что внутри репо, если решите использовать:

Документ | Зачем
README.md | Быстрый старт, CLI-флаги, prereqs, устранение неполадок
docs/integration-contract.md | Контракт для серверных вызывающих: stdin/stdout, коды выхода, мэппинг моделей отказов
docs/RUNBOOK.md | Триаж в первые 30 минут, ошибки сборки, инциденты на продакшене
docs/architecture.md | C4-обзор архитектуры для тех, кто хочет понять как устроено внутри
tests/golden_v0.rs | Golden-batch регрессия: 10 эталонных файлов, пере-записываются при смене libwebp

Сборка (Rust 1.75+ и libwebp dev headers):

# partial — фрагмент CLI-команд; runnable на Ubuntu/Debian/Arch/macOS хосте.
git clone https://github.com/elriseio/fast-image-converter
cd fast-image-converter
cargo build --release
# → target/release/fast-image-converter

Что инструмент не делает (чтобы не тратить время на оценку): GIF/TIFF/AVIF (Wave 2 в Roadmap, не реализовано), distributed batch (single-host multi-core), in-process library API (бинарь — единственная публичная поверхность), анимированный GIF/APNG (только первый кадр), явные ICC-трансформации (проходят через image crate defaults).