Pull to refresh

Comments 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 МПикс, а сохранять её в таком виде не стоит.

Стоит или не стоит — зависит от задачи, тут уже не разработчику библиотек решать.

Так сделайте измерения на 72 виртуальных CPU и покажите результат. Без умножений и экстраполяций. Мы показали как есть на реальном железе.
Стоимость будет меньше, а скорость ещё выше вот на этой видеокарте:
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             |

Спасибо за подсказку с тредами, я был уверен, что это бесполезная фигня )

Спасибо за информацию, мы у Вас тоже учимся :)
Будем использовать этот софт вместе с ценой для Amazon в качестве Price/Performance эталона для дальнейших разработок.
Извините, я наверное не так выразился. Как я понял вы продаёте своё решение в виде SDK? Вот цена этого SDK и интересует. А цену теслы и прочих инстансов мы знаем.
Спасибо.

Я ничего не продаю. Pillow-SIMD и libjpeg-turbo совершенно бесплатны в том числе для коммерческого использования.

Да, это, конечно-же, не Вам был вопрос, а feydorser. Может он откликнется…
Есть разные варианты лицензирования SDK в зависимости от набора модулей и условий использования, а также есть порядок работы с заказчиками. Присылаете запрос, подробное описание задачи, что хотите получить, какое есть железо, как хотите интегрировать SDK в свой софт и т.д. Мы присылаем наше предложение.
Для сжатия, используются вычисления с одинарной или двойной точностью?
В алгоритме JPEG есть несколько стадий, которые не стоит считать в целых числах: цветовое преобразование, дискретное косинусное и квантование. Мы их считаем с одинарной точностью.
Я знаю, что libjpeg-turbo считает в целых числах, но не думаю, что это правильно. Получается, что при кодировании приходится делать три округления, когда можно сделать только одно. То же самое происходит и при декодировании. Джипег не является беспотерьным алгоритмом именно из-за этих округлений, но ничего хорошего они не несут.
Джипег не является беспотерьным алгоритмом именно из-за этих округлений

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

Расскажите нам про DCT.
Ок, округляйте после каждой стадии, как и libjpeg-turbo 2.0.
Подскажите пожалуйста, есть ли смысл изучать CUDA если далеко не все смогут запустить ваши проекты у себя (т.к. эта проприетарщина только у NVIDIA). Может, для проектов подобного рода лучше выбрать OpenCL? Ведь он гарантированно есть на любой видеокарте, а значит вы не столкнётесь внезапно с тем что у вашего заказчика есть рабочие станции с видеокартами AMD и эту CUDA придётся срочно портировать на OpenCL. Почему не писать на OpenCL изначально, чтобы избежать подобных рисков?
Можно для начала посмотреть на рыночные доли NVIDIA и AMD — они сильно отличаются в пользу NVIDIA, т.е. таких карточек намного больше. Большой плюс NVIDIA в том, что у них есть полный спектр видеокарт — мобильные, для ноута/десктопа, серверные. Вот в этом документе есть впечатляющий список приложений на CUDA — www.nvidia.com/content/gpu-applications/PDF/gpu-applications-catalog.pdf
Приложений для CUDA не много, а очень много. К сожалению, никогда не видел аналогичного документа для OpenCL.

Универсальность OpenCL может являться преимуществом только для неоптимизированных решений. Как только захотите добиться максимальной производительности, сразу же придётся принимать во внимание особенности железа, т.е. универсализм OpenCL тут же исчезнет. Будет проприетарщина, но уже на OpenCL.

Обычно заказчик покупает рабочие станции для оптимизированных решений, а не наоборот. А срочно портировать OpenCL на CUDA вряд ли придётся: такая разработка и отладка обычно занимает много времени, поэтому лучше сразу подумать, на каком железе и где всё это будет работать. Конечно, есть ситуации когда это не так (например, случай с Маком), но я говорю о часто встречаемых, которых видел немало.

С моей точки зрения есть смысл изучать CUDA. Тем более сейчас, когда тематика по нейросетям и искусственному интеллекту явно на подъёме. По этой тематике приложений очень много и работы тоже.
С год назад баловался майнингом, запускал OpenCL- и CUDA-версии разных майнеров. Результат был практически один в один. Ну, конечно, задачка та ещё
Интересно бы узнать относительную производительность. То есть, та же задача, с теми же картинками, только без CUDA (напр. libjpeg-turbo), сколько времени занимала?
Интересно, а не быстрее ли будет, если делать ресайз в частотной области, а не в пространственной — не выполняя прямое и обратное косинусное преобразование? Хотя бы при кратном ресайзе. Кто нибудь пробовал это делать?
Это даже для свертки далеко не всегда быстрее. Думаю для ресайза вообще бесперспективно.
Дело в том, что дискретное косинусное преобразование выполняется не над всем кадром, а над каждым блоком 8х8 исходного изображения. Поэтому вряд ли можно в частотной области сделать ресайз для джипега с произвольным коэффициентом масштабирования. А для ресайза в степень двойки есть такой простой пример: компонент DC (пиксел с координатами 0,0 в блоке 8х8 после косинусного преобразования) является средней яркостью блока 8х8, но даже ресайз в 8 раз по ширине и высоте так делать нельзя, получится очень грубо. Такое усреднение даёт очень приблизительный результат и любой бикубик или ланцош сделает всё гораздо лучше.

В случае с JPEG2000, где по сути используется пространственно-частотное преобразование, ресайз в 2 раза по ширине и высоте — это компонента LL вейвлетного разложения, которая получается очень просто, но там разложение делают для всей картинки или для отдельного тайла, но не для блока 8х8.
В оффлайне с помощью ImageMagick, OpenCV или аналогичного софта, сохраняем цветовой профиль

Забавно, что вы упоминаете OpenCV, потому что в ней нет вообще никакой возможности получить цветовой профиль. OpenCV — это библиотека компьютерного зрения, а не работы с зоопарком графических форматов.


затем утилитой jpegtran добавляем рестарт-маркеры с заданным фиксированным интервалом.

Интересно было бы узнать оптимальный интервал, хотя бы для конкретно вашего декодера.

Про OpenCV — это ошибка, исправлю.
Наши бенчмарки декодера были сделаны для рестарт интервала 2. Наилучший для нашего декодера рестарт интервал равен единице, но при этом размер файла может увеличиться до 15% по сравнению со случаем без маркеров, поэтому остановились на двойке.
UFO just landed and posted this here
Сжатая картинка занимает 0.2-0.5 МБайт, память видеокарты не более 24 ГБ, так что прямо в GPU можно хранить не более 100 тысяч таких картинок. Если количество изображений для кеша имеет такой порядок, то можно попробовать. Правда, сэкономить удастся только на копировании в видеокарту, а это и так делается очень быстро через x16 PCIE-3.0, т.е. со скоростью порядка 10-11 ГБайт/с. Возможно, такой кеш лучше хранить на быстром диске где-то рядом.
UFO just landed and posted this here
Если хранить несжатые изображения, то на это потребуется в 10-15 раз больше места. Размер памяти на Tesla V100 равен 32 ГБ. Так, конечно, было бы быстрее, но кажется, что для кеша мало места.
Sign up to leave a comment.

Articles