Комментарии 31
Давайте вместе посчитаем. Возьмем для примера цены AWS EC2:
p3.2xlarge
инстанс имеет на борту одну NVIDIA Tesla V100 GPU и стоит $3.06 в час.
Столько же стоит c5.18xlarge
инстанс на новых процессорах Intel c 36 ядрами.
Сравнивать буду с одной из самых быстрых бесплатных библиотек для обработки изображений Pillow-SIMD.
На странице бенчмарков пока что нет результатов c5 инстансов, поэтому возьмем результаты для c4. Уверяю, что c5 работают не медленнее.
Сразу выпишу интересующие нас операции:
Jpeg load speed = 181 Mpx/s
Lanczos resize speed = 300 Mpx/s
Sharp filter speed = 524 Mpx/s
Jpeg save speed = 166 Mpx/s
Скорости ресайза именно в два раза нет (потому что при уменьшении в кратное количество раз всегда можно считерить и сделать быстрее, так что для бенмарков такие значения лучше не брать), есть результаты для уменьшения в 1,25 раз (238 Mpx/s) и результаты для уменьшения в 8 раз (658 Mpx/s). Мне лень сейчас делать бенчмарк, поэтому я «на глазок» взял 300 Mpx/s.
Все результаты приведены для одного ядра.
Сравнивать будем на 2K разрешении, так как это разрешение показало значительно более хорошие результату на Tesla V100.
Jpeg load time = 1920*1080 / 10**6 / 181 = 0.0115 s
Lanczos resize time = 1920*1080 / 10**6 / 300 = 0.0069 s
Sharp filter time = 960*540 / 10**6 / 524 = 0.001 s
Jpeg save time = 960*540 / 10**6 / 166 = 0.003 s
Total speed = 1 / (0.0115 + 0.0069 + 0.001 + 0.003) = 44 op/s
Итого, 44 операции в секунду на одном ядре. Или 44×36 = 1584 операции в секунду на процессоре за ту же цену.
Плюс практически пропорциональное увеличение производительности при уменьшении входного разрешения (как вы видите, для видеокарты при переходе от 2К к 1К производительность практически не растет). Плюс отсутствует необходимость проставлять рестарт-маркеры или иным образом модифицировать исходный контент. Плюс большая гибкость в выборе и добавлении других операций (не требуется перекомпиляция) или форматов. Плюс возможность разрабатывать и тестировать на обычном рабочем ПК или ноутбуке.
Минус только в задержке. В случае обработки на CPU одно изображение все равно будет ресайзится минимум за 23 мс, тогда как на GPU этот показатель от 1,1 до 1,5 мс.
Тут оказывается вышла libjpeg-turbo 2.0, я потестил, можно накинуть еще 15% к скорости распаковки и запаковки. А так как это самая дорогая операция, мы получаем уже не 44, а 48 операций в секунду на ядро, или 1722 операции в секунду за те же деньги.
Спасибо за информацию, тут есть что обсудить.
1. Для видеокарты мы рассмотрели худший вариант, т.е. работу с изображениями небольшого разрешения, поскольку при этом видеокарта остаётся недозагруженной. Этот вариант является наилучшим для CPU, особенно если ещё предположить, что производительность будет расти линейно с увеличением количества ядер. Но нужно принимать во внимание и такие подробности:
а) В этом процессоре частота 3.5 ГГц только в турбо-режиме (в обычном 2.5 ГГц) и неизвестно, какая частота будет при максимальной загрузке. Это нужно проверять, без тестов нельзя.
б) 36 виртуальных ядер (18 реальных) — это отлично. Мы как раз сделали тесты с libjpeg-turbo и точно знаем, что умножение на 36 является излишне оптимистичным. Для декодирования джипегов в многопоточном режиме нужно умножать примерно на 20 для такого железа, но это тоже нужно проверять для всей схемы обработки, а не только для декодера.
2. Что касается цен, то в данном случае Tesla V100 — не оптимальный вариант, по стоимости сравнимый с последними CPU Intel Xeon. В этой видеокарте есть ядра для аппаратного ускорения нейросетей, которые в данной задаче никак не используются. Поэтому я и написал, что оптимальный по цене вариант можно выбрать для заданного набора параметров задачи, так что практически в любом варианте подойдут менее дорогие видеокарты. Тогда при корректном сравнении придётся взять более слабый CPU и ситуация станет совсем другой.
3. Мы сделали тест с ресайзом в 2 раза исключительно для удобства понимания, никакие упрощённые алгоритмы при этом не использовались. Чтобы тест показывал производительность с разных сторон, имеет смысл уменьшать картинку 1920х1080 не в 2-4 раза, а до разрешения 1919х1079 (можно до 1904х1071, чтобы не искажать отношение сторон). Тогда мы и увидим настоящую производительность ресайза. Когда делают ресайз 2560->320, то таким образом явно улучшают результат для CPU. Ситуация, когда нужен ресайз менее чем в 2 раза, вполне рабочая. У нас ресайз в 2 раза делается за 0.27 мс, а ресайз до 1919х1079 занимает 0.39 мс. Интересно, сколько получается на CPU?
как вы видите, для видеокарты при переходе от 2К к 1К производительность практически не растет
4. Действительно, при уменьшении не растёт, при увеличении — растёт. Мы как раз работаем на оптимизацией работы с маленькими разрешениями, так что точно будет быстрее. Возможно, в пару раз. А для картинок 4К производительность получается в разы больше, поскольку при таком разрешении можно загрузить видеокарту как следует.
5. Что касается предварительной подготовки изображений, то делать её придётся в любом случае. Пользователь запросто может прислать картинку на 8 или 12 МПикс, а сохранять её в таком виде не стоит. Поэтому придётся декодировать, ресайзить и кодировать в оффлайне. В этот момент мы и добавляем маркеры нашим кодером, а jpegtran обычно используется только в тех случаях, когда нужно добавлять маркеры без выполнения ресайза, т.е. очень редко. Если же не добавлять маркеры, а использовать исходное большое разрешение, то для такого случая производительность нашего решения будет ещё выше, если сравнивать с аналогичным решением на CPU.
36 виртуальных ядер (18 реальных) — это отлично.
У c5.18xlarge
инстансов 72 виртуальных ядра, 36 реальных. Так что умножение на 36 является пессимистичным в моих расчетах.
В данном случае Tesla V100 — не оптимальный вариант. Оптимальный по цене вариант можно выбрать для заданного набора параметров задачи.
Тут уж извините, вы сами выбрали что применить для данной задачи.
Когда делают ресайз 2560->320, то таким образом явно улучшают результат для CPU.
Я не понимаю, при чем тут 2560→320. Результаты для ресайза 2560→2048 и 2560→320 — это те цифры, которые были у меня на руках без дополнительных бенчмарков. Я их довольно грубо экстраполировал до 2048→1024, но не думаю, что моя оценка имеет точность меньше ±25%.
Интересно, сколько получается на CPU?
Будет что-то близкое к результату 2560→2048.
Пользователь запросто может прислать картинку на 8 или 12 МПикс, а сохранять её в таком виде не стоит.
Стоит или не стоит — зависит от задачи, тут уже не разработчику библиотек решать.
Стоимость будет меньше, а скорость ещё выше вот на этой видеокарте:
www.nvidia.com/en-us/design-visualization/quadro/rtx-6000
Мы за пользователя ничего не решаем, я всего лишь сказал, что для 8 МПикс картинок производительность решения на видеокарте станет больше благодаря более высокому разрешению картинки. Это просто факт.
Так сделайте измерения на 72 виртуальных CPU и покажите результат.
А вы мне что? )
Ну ладно, раз вы так вежливо попросили, сделаю несколько более приближенный к реальности тест.
Во-первых я сделал в репозитории pillow-perf ветку fastvideo-competition, в которой переписан тест full_cycle, чтобы максимально соответствовать тому, что делаете вы. Там 4 тесткейса:
- Load+save — только загрузка и сохранение jpeg.
- +resize — добавляется ресайз посередине. По большей части два этих кейса просто прогревают CPU, чтобы успел выключиться TurboBoost, про который вы упоминали
- +sharp — добавляется шарп. Это основной тесткейс, результаты которого нас интересуют
- +sharp — еще один тест, который дублирует предыдущий, чтобы когда какой-то процесс заканчивал работу, нагрузка на CPU не падала и результат не искажался
Тесты запускаются командой ./pillow-perf/testsuite/run.py full_cycle -s 1920x1080 -n 1024
и никак иначе, без параметра -s
во-первых будут неправильно считаться мегапиксели в секунду, во-вторых я поставил там assert ) Результаты будут выводиться для медианного запуска из 1024 попыток.
Теперь, где будем запускать. Ваше предложение запускать «на 72 виртуальных CPU» очень заманчиво, но не выглядит разумным. Запуская кучу инстансов помельче вы получаете отказоустойчивость и лучшую гранулярность (очень маловероятно, что вам нужно обрабатывать ровно 1500 картинок в секунду, ни больше, ни меньше). Впрочем, жестить и запускать 36 машин тоже не стоит. Я остановился на одном инстансе c5.2xlarge
с 8 виртуальными и 4 физическими ядрами. Думаю, не нужно доказывать, что два запущенных рядом одинаковых инстанса будут работать ровно в два раза быстрее, чем один. А 9 ровно в 9 раз быстрее, что по цене и производительности в точности равно одному c5.18xlarge
инстансу.
Всего будет 6 тестов: один поток, 4 потока, 8 потоков и всё еще раз, но с libjpeg-turbo 2.0.
Многопоток будет запускаться такой командой:
time (./run.py full_cycle -s 1920x1080 -n 1024 &\
./run.py full_cycle -s 1920x1080 -n 1024 &\
./run.py full_cycle -s 1920x1080 -n 1024 &\
./run.py full_cycle -s 1920x1080 -n 1024 &\
wait)
Ладно, поехали.
Один поток, libjpeg-turbo 1.4.2
$ time ./run.py full_cycle -s 1920x1080 -n 1024
Full cycle 1920×1080 RGB image
Load+save 0.02544 s 81.51 Mpx/s
+resize 0.02222 s 93.33 Mpx/s
+sharp 0.02335 s 88.81 Mpx/s
+sharp 0.02335 s 88.80 Mpx/s
real 1m36.680s
user 1m36.228s
sys 0m1.004s
Получается 0.02335 s на процессинг одной картинки. Моя оценка была, напомню, 0.0224 s. Дальше в секундах измерять не имеет смысла, буду писать фрупут в мегапикселях и в операциях в секунду. Ну и не буду писать результаты остальных тестов, чтобы экономить место.
4 потока, libjpeg-turbo 1.4.2
+sharp 0.02317 s 89.49 Mpx/s
+sharp 0.02352 s 88.16 Mpx/s
+sharp 0.02354 s 88.09 Mpx/s
+sharp 0.02356 s 88.00 Mpx/s
real 1m37.357s
user 6m25.228s
sys 0m2.800s
1/0.02317 + 1/0.02352 + 1/0.02354 + 1/0.02356 = 171 операция в секунду
89.49 + 88.16 + 88.09 + 88.00 = 353 Mpx/s
Проверяем: 353/1920/1080*10**6 = 171. Сходится, как ни странно )
8 потоков, libjpeg-turbo 1.4.2
+sharp 0.03690 s 56.19 Mpx/s
+sharp 0.03690 s 56.19 Mpx/s
+sharp 0.03805 s 54.50 Mpx/s
+sharp 0.03803 s 54.53 Mpx/s
+sharp 0.03806 s 54.48 Mpx/s
+sharp 0.03802 s 54.53 Mpx/s
+sharp 0.03810 s 54.43 Mpx/s
+sharp 0.03811 s 54.42 Mpx/s
real 2m36.108s
user 20m31.172s
sys 0m6.672s
439 Mpx/s, 212 запусков в секунду.
Один поток, libjpeg-turbo 2.0
$ time ./run.py full_cycle -s 1920x1080 -n 1024
Full cycle 1920×1080 RGB image
Load+save 0.02120 s 97.81 Mpx/s
+resize 0.01968 s 105.37 Mpx/s
+sharp 0.02063 s 100.53 Mpx/s
+sharp 0.02057 s 100.79 Mpx/s
real 1m24.265s
user 1m23.808s
sys 0m0.912s
Полный вывод, чтобы просто сравнить с libjpeg-turbo 1.4.2.
4 потока, libjpeg-turbo 2.0
+sharp 0.02049 s 101.20 Mpx/s
+sharp 0.02079 s 99.74 Mpx/s
+sharp 0.02085 s 99.45 Mpx/s
+sharp 0.02118 s 97.91 Mpx/s
real 1m25.715s
user 5m36.984s
sys 0m2.768s
398 Mpx/s, 192 запусков в секунду.
8 потоков, libjpeg-turbo 2.0
+sharp 0.03201 s 64.77 Mpx/s
+sharp 0.03203 s 64.74 Mpx/s
+sharp 0.03246 s 63.88 Mpx/s
+sharp 0.03246 s 63.88 Mpx/s
+sharp 0.03303 s 62.77 Mpx/s
+sharp 0.03303 s 62.77 Mpx/s
+sharp 0.03296 s 62.91 Mpx/s
+sharp 0.03295 s 62.92 Mpx/s
real 2m10.901s
user 17m12.692s
sys 0m5.824s
508 Mpx/s, 245 запусков в секунду.
Ну и сводная табличка:
| | MPx/s | Op/s c5.2xlarge | Op/s c5.18xlarge |
|-------------|-------|-----------------|------------------|
| 1c, lib 1.4 | 89 | 43 | - |
| 4c, lib 1.4 | 353 | 171 | 1539 |
| 8c, lib 1.4 | 439 | 212 | 1908 |
| 1c, lib 2.0 | 101 | 48 | - |
| 4c, lib 2.0 | 398 | 192 | 1728 |
| 8c, lib 2.0 | 508 | 245 | 2205 |
Спасибо за подсказку с тредами, я был уверен, что это бесполезная фигня )
Спасибо.
Я ничего не продаю. Pillow-SIMD и libjpeg-turbo совершенно бесплатны в том числе для коммерческого использования.
Однако же в libjpeg-turbo все операции выполняются в целых числах: https://libjpeg-turbo.org/About/SIMDCoverage
Джипег не является беспотерьным алгоритмом именно из-за этих округлений
Очень странно такое слышать от людей, занимавшихся разработкой кодека джипег. Джипег не является беспотерьным алгоритмом вообще не из-за округлений )
Приложений для CUDA не много, а очень много. К сожалению, никогда не видел аналогичного документа для OpenCL.
Универсальность OpenCL может являться преимуществом только для неоптимизированных решений. Как только захотите добиться максимальной производительности, сразу же придётся принимать во внимание особенности железа, т.е. универсализм OpenCL тут же исчезнет. Будет проприетарщина, но уже на OpenCL.
Обычно заказчик покупает рабочие станции для оптимизированных решений, а не наоборот. А срочно портировать OpenCL на CUDA вряд ли придётся: такая разработка и отладка обычно занимает много времени, поэтому лучше сразу подумать, на каком железе и где всё это будет работать. Конечно, есть ситуации когда это не так (например, случай с Маком), но я говорю о часто встречаемых, которых видел немало.
С моей точки зрения есть смысл изучать CUDA. Тем более сейчас, когда тематика по нейросетям и искусственному интеллекту явно на подъёме. По этой тематике приложений очень много и работы тоже.
В случае с JPEG2000, где по сути используется пространственно-частотное преобразование, ресайз в 2 раза по ширине и высоте — это компонента LL вейвлетного разложения, которая получается очень просто, но там разложение делают для всей картинки или для отдельного тайла, но не для блока 8х8.
В оффлайне с помощью ImageMagick, OpenCV или аналогичного софта, сохраняем цветовой профиль
Забавно, что вы упоминаете OpenCV, потому что в ней нет вообще никакой возможности получить цветовой профиль. OpenCV — это библиотека компьютерного зрения, а не работы с зоопарком графических форматов.
затем утилитой jpegtran добавляем рестарт-маркеры с заданным фиксированным интервалом.
Интересно было бы узнать оптимальный интервал, хотя бы для конкретно вашего декодера.
Быстрый ресайз джипегов на видеокарте