Pillow-SIMD

Прискорення операцій у 2.5 рази порівняно з Pillow і в 10 порівняно з ImageMagick

Pillow-SIMD — це «форк-послідовник» бібліотеки роботи з зображеннями Pillow (яка сама є форком бібліотеки PIL, нині покійної). «Послідовник» означає, що проект не стає самостійною, а буде оновлюватися разом з Pillow і мати ту ж нумерацію версій, тільки з суфіксом. Я сподіваюся більш-менш оперативно випускати версії Pillow-SIMD відразу після виходу версій Pillow.
Чому SIMD
Є кілька способів поліпшення продуктивності обробки зображень (та і всіх інших речей, напевно, теж).
  1. Можна використовувати більш кращі алгоритми, які дають такий же результат.
  2. Можна зробити більш швидку реалізацію існуючого алгоритму.
  3. Можна підключити більше обчислювальних ресурсів для рішення тієї ж задачі: додаткові ядра 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 16x16 Bilinear 27.0 217 437 710
Bicubic 10.9 115 232 391
Ланцоша 6.6 76.1 157 265
Resize to 320x180 Bilinear 32.0 166 410 612
Bicubic 16.5 92.3 211 344
Ланцоша 11.0 63.2 136 223
Resize to 2048x1155 Bilinear 20.7 87.6 229 265
Bicubic 12.2 65.7 140 171
Ланцоша 8.7 41.3 100 126
Blur 1px 8.1 17.1 37.8
10px 2.6 17.4 39.0
100px 0.3 17.2 39.0
1920×1280 RGB Resize to 16x16 Bilinear 41.6 196 426 750
Bicubic 18.9 102 221 379
Ланцоша 13.7 68.6 140 227
Resize to 320x180 Bilinear 27.6 111 303 346
Bicubic 14.5 66.3 164 230
Ланцоша 9.8 44.3 108 143
Resize to 2048x1155 Bilinear 9.1 20.7 71.1 69.6
Bicubic 6.3 16.9 53.8 53.1
Ланцоша 4.7 14.6 40.7 41.7
Blur 1px 8.7 16.2 35.7
10px 2.8 16.7 35.4
100px 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.ЛАНЦОША == Ланцоша
Для тестування були використані такі скрипти.
Чому 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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.