Pillow-SIMD
Pillow-SIMD — это «форк-последователь» библиотеки работы с изображениями Pillow (которая сама является форком библиотеки PIL, ныне покойной). «Последователь» означает, что проект не становится самостоятельным, а будет обновляться вместе с Pillow и иметь ту же нумерацию версий, только с суффиксом. Я надеюсь более-менее оперативно выпускать версии Pillow-SIMD сразу после выхода версий Pillow.
Почему SIMD
Есть несколько способов улучшения производительности обработки изображений (да и всех остальных вещей, наверное, тоже).
- Можно использовать более лучшие алгоритмы, которые дают такой же результат.
- Можно сделать более быструю реализацию существующего алгоритма.
- Можно подключить больше вычислительных ресурсов для решения той же задачи: дополнительные ядра CPU, GPU.
Самое классное, когда вы можете использовать более быстрый алгоритм, как когда в Pillow 2.7 Гауссово размытие на основе сверток было заменено размытием последовательностью box-фильтров. К сожалению, число таких фокусов весьма ограничено. Также очень заманчива идея использовать больше вычислительных ресурсов. Но к сожалению, часто их либо нет, либо они стоят дополнительных денег (как в случае с арендуемыми серверами). Использовать же GPU для вычислений вообще нетривиальная задача, связанная с подбором определенного железа и правильной настройкой драйверов. Остается самый надежный способ — попытаться заставить существующий код работать быстрее на существующем железе. И тут SIMD-инструкции подходят как нельзя лучше.
SIMD означает: «одна инструкция, много данных» (single instruction, multiple data). В классических программах мы берем операнды, выполняем операцию, сохраняем результат. В случае SIMD мы берем сразу пачку операндов, делаем одно и то же действие над всеми разом и сохраняем пачку результатов. Для процессора это проще, чем несколько раз выполнить одинаковые действия. Существует огромное количество расширений команд процессоров с SIMD-инструкциями, например: MMX, SSE-SSE4, AVX, AVX2, AVX512, NEON.
В текущей версии Pillow-SIMD может быть скомпилирован с использованием расширений SSE4 (по умолчанию), либо AVX2.
Статус проекта
Pillow-SIMD годится для продакшена. Различные версии Pillow-SIMD уже больше года работают на серверах Uploadcare. Uploadcare — это сервис для хранения и обработки пользовательского контента и главный спонсор Pillow-SIMD.
На текущий момент следующий операции ускорены в SIMD-версии:
- Ресайз (ресемплинг на основе сверток): SSE4, AVX2
- Гауссово размытие и box-фильтры: SSE4
Производительность
Цифры означают количество обработанных мегапикселей исходного изображения в секунду. Например, если ресайз изображения размером 7712×4352 был выполнен за 0.5 секунд, производительность будет 67.1 Mpx/s.
Уже в процессе редактирования я понял, что у меня кажется путаница и в мегапикселе для ImageMagick 10^6 пикселей, а в мегапикселе для Pillow — 2^20. Но это не сильно влияет на общую картину.
Протестированы:
ImageMagick 6.9.3-8 Q8 x86_64
Pillow 3.2.0
Pillow-SIMD 3.2.0.post2
Source | Operation | Filter | IM | Pillow | SIMD SSE4 | SIMD AVX2 |
---|---|---|---|---|---|---|
7712×4352 RGB | Resize to 16×16 | Bilinear | 27.0 | 217 | 437 | 710 |
Bicubic | 10.9 | 115 | 232 | 391 | ||
Lanczos | 6.6 | 76.1 | 157 | 265 | ||
Resize to 320×180 | Bilinear | 32.0 | 166 | 410 | 612 | |
Bicubic | 16.5 | 92.3 | 211 | 344 | ||
Lanczos | 11.0 | 63.2 | 136 | 223 | ||
Resize to 2048×1155 | Bilinear | 20.7 | 87.6 | 229 | 265 | |
Bicubic | 12.2 | 65.7 | 140 | 171 | ||
Lanczos | 8.7 | 41.3 | 100 | 126 | ||
Blur | 1 px | 8.1 | 17.1 | 37.8 | ||
10 px | 2.6 | 17.4 | 39.0 | |||
100 px | 0.3 | 17.2 | 39.0 | |||
1920×1280 RGB | Resize to 16×16 | Bilinear | 41.6 | 196 | 426 | 750 |
Bicubic | 18.9 | 102 | 221 | 379 | ||
Lanczos | 13.7 | 68.6 | 140 | 227 | ||
Resize to 320×180 | Bilinear | 27.6 | 111 | 303 | 346 | |
Bicubic | 14.5 | 66.3 | 164 | 230 | ||
Lanczos | 9.8 | 44.3 | 108 | 143 | ||
Resize to 2048×1155 | Bilinear | 9.1 | 20.7 | 71.1 | 69.6 | |
Bicubic | 6.3 | 16.9 | 53.8 | 53.1 | ||
Lanczos | 4.7 | 14.6 | 40.7 | 41.7 | ||
Blur | 1 px | 8.7 | 16.2 | 35.7 | ||
10 px | 2.8 | 16.7 | 35.4 | |||
100 px | 0.4 | 16.4 | 36.2 |
Pillow всегда быстрее, чем ImageMagick, а Pillow-SIMD быстрее чем Pillow примерно в 2–2.5 раза для SSE4-версии. В основном, AVX2-версия оказывается быстрее чем ImageMagick в 10–15 раз.
Тесты выполнялись на Ubuntu 14.04 64-bit, запущенной на процессоре Intel Core i5 4258U с AVX2. Все тесты использовали только одно ядро процессора.
Производительность ImageMagick была измерена утилитой командной строки convert с аргументами -verbose
и -bench
. Выбранные фильтры в точности соответствуют существующим в Pillow фильтрам:
PIL.Image.BILINEAR == Triangle
PIL.Image.BICUBIC == Catrom
PIL.Image.LANCZOS == Lanczos
Для тестирования были использованы такие скрипты.
Почему Pillow такой быстрый
Тут нет никаких трюков, для тестов использовались высококачественные методы ресайза и размытия. Результаты практически попиксельно совпадают с небольшой погрешностью. Разница только в эффективности самих алгоритмов. В Pillow 2.7 ресемплинг был переписан с использованием предварительно вычисленных коэффициентов, меньшим использованием чисел с плавающей точкой и транспонированием, эффективно использующим кэш процессора.
Почему Pillow-SIMD еще быстрее
Конечно же из-за использования SIMD-команд. Но у меня еще несколько мыслей, как можно улучшить этот результат.
- Эффективная работа с памятью В настоящий момент каждый пиксель загружается в SSE-регистр из памяти по отдельности, в то время как в один SSE-регистр возможно прочитать 4 пикселя за раз.
- Вычисления на целых числах Не смотря на то, что современные процессоры очень эффективно работают с числами с плавающей точкой, есть две причины полагать, что работа с целыми числами будет эффективнее: операции над целыми алгоритмически проще; для работы с ними не требуется дополнительных ковертаций.
- Выравнивание данных в памяти загрузка и выгрузка данных из SIMD-регистров выполняется быстрее, если адреса в памяти, с которыми идет обмен, выровнены.
Почему бы не влить изминения обратно в Pillow
Если коротко — это очень сложно. Pillow поддерживает большой количество архитектур, не только x86. Но даже на x86 Pillow для некоторых платформ распространяется в виде скомпилированных исполняемых файлов. Чтобы иметь возможность использовать SIMD-команды в коде, нужно передавать компилятору аргументы, разрешающие использование самых продвинутые инструкций, которые мы хотим использовать: -mavx2
. После этого нужно делать проверку возможностей процессора во время выполнения и включать ту или иную ветку кода в зависимости от них. Проблема в том, что такие аргументы автоматически компируют код, спрятанный под условия препроцессора if (__AVX2__)
и ниже, который может ни иметь никаких проверок времени выполнения. Самое печальное, что такой код действительно находится, по крайней мере при компиляции GCC и исполняемые файлы без явного использования AVX2, но собранные с -mavx2
, начинают вылетать. Разумеется, можно собирать разные версии библиотеки с разными опция компилятора и динамическим их подключать, но это [см. начало этого параграфа].
Установка
Хорошие новости, что для установки SSE4 версии достаточно написать как обычно pip install pillow-simd
и если ваш процессор умеет в SSE4 (думаю, вероятность этого около 95%), все пойдет замечательно. Не забудьте удалит оригинальный пакет Pillow.
Если вы хотите собрать AVX2 версию, то нужно передать компилятору дополнительные флаги. Проще всего это сделать задав переменную окружения CC
во время установки и компиляции.
$ pip uninstall -y pillow-simd ; CC="cc -mavx2" pip install pillow-simd
Иногда бывает, что зависимость от Pillow есть не только у вас, но и у других пакетов, которые вы используете. И даже если эти пакеты не особо нуждаются в быстром ресемплинге, они все равно устанавливают Pillow без SIMD, который может импортироваться первым. Для этого может пригодиться такой хак при установке с Гитхаба:
$ pip install -e git+https://github.com/uploadcare/pillow-simd.git@v3.2.0.post3#egg=pillow
Тогда во время установки другого пакета с зависимостью от Pillow, еще одна версия Pillow ставиться не будет:
$ pip install xhtml2pdf -e git+https://github.com/uploadcare/pillow-simd.git@v3.2.0.post3#egg=pillow