GPU-ускорение FFmpeg. Видите прибавку в скорости? И я нет. А она должна быть…
Привет, Хабр! С вами Матвей Мочалов, и сегодня у нас небольшая лабораторная работа. Вспомним, что GPU нужны не только для нейронок и AI — еще они могут ускорять много других полезных задач. А конкретно мы сравним разницу в скорости между работой FFmpeg на процессоре и на видеокарте Nvidia.
В ролях у нас гибридный ноутбук под Linux с мобильной видеокартой RTX 3050Ti и процессором Ryzen 5 5600H. Также в массовке участвует удалённый тестовый сервер с Xeon и заглушкой в PCI слот, которую дядя Дженсен Хуанг решил по доброте сердечной добавить в линейку Quadro.
К слову, ранее мы уже немного работали с FFmpeg, разбираясь с транскодированием и устройством видеоконтейнеров тут.
Подготовка к тесту
Сначала посмотрим, на чём будем проводить тесты.
Начнём с моей личной машины, на которой крутится EndeavourOS (Arch):
Ядро — 6.9.3-arch1–1, 8 гигов DDR4 на частоте 3200Mhz + 8 гигов физического swap и ещё 8 за счёт zram. CPU — ryzen 5600h 6/12 с теплопакетом 35w (хотя, если верить nvtop, то доходит до 45w) и GPU — RTX 3050ti на 4gb VRAM с теплопакетом 60w.
Версия драйвера Nvidia — 550.78–7, FFmpeg — 2:6.1.1–7.
Тестовый сервер — AlmaLinux 9.3, ядро 6.6.11–1.el9.x86_64, 16 гигов DDR3 и 2 гига swap.
CPU — Xeon 2630, 6 ядер с отключенным SMT, GPU — Quadro P400.
Версия драйвера Nvidia — 550.54.15, FFmpeg — свой билд с оптимизациями под CUDA.
Отдельно сделаю ещё акцент на том, что Xeon — это старичок 2012 года, а Quadro — 2017, но, не смотря на возраст, результаты будут весьма неоднозначными.
Теперь скачаем тестовое видео. Пусть это будет детище Blender Foundation — Big Buck Bunny.
```bash
wget https://download.blender.org/demo/movies/BBB/bbb_sunflower_2160p_30fps_normal.mp4.zip
unzip bbb_sunflower_2160p_30fps_normal.mp4.zip
```
Тестирование
Для начала прогоним сравнительный тест на моём ноуте без GPU-ускорения.
Тестировать будем рескейлинг с разрешения 2160p до 1080p.
```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf scale=1920:1080 -c:v mpeg4 -preset medium output_no_acc.mp4
```
И повторим так 5 раз для надёжности, получая вот такие результаты:
Номер теста | Скорость |
1 | 4.09x |
2 | 3.95x |
3 | 4.22x |
4 | 4.17x |
5 | 4.26x |
Средний результат — 4.138x.
Однако тест выходит не очень честным, так как, судя по нагрузке на GPU, в nvtop резко растёт потребление питания у iGPU и процент нагрузки на графическое ядро. При этом потребление видеопамяти почти не меняется. Xeon же в тестовом сервере не обладает встроенной графикой вовсе.
Теперь на тестовом сервере:
Номер теста | Скорость |
1 | 3.54x |
2 | 3.52x |
3 | 3.52x |
4 | 3.55х |
5 | 3.54х |
В итоге среднее — 3.534x.
Современный Ryzen 5600H на 14.59% оказался быстрее старичка Xeon 2630 — результат, откровенно говоря, не сильно впечатляющий. Между процессорами пропасть почти 10 лет (ряженка вышла в 2021 году). Так ещё к тому же на её стороне была включенная гиперпоточность, хотя что-то мне подсказывает, что в этой операции FFmpeg работал в однопоточном режиме. Кроме того, ей помогала интегрированная графика. Серверный труженик и любитель сборщиков ПК с Алиэкспресс интеграшкой не обладает, ещё и гиперпоточность была задушена.
Впрочем, опустим странности и сомнительный прогресс производительности процессоров для другого поста. Перейдём к тому, что интересно в первую очередь — GPU-ускорению.
CUDA
Начнём снова с моего ноутбука и повторим тесты также по 5 раз.
```bash
ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```
Номер теста | Скорость |
1 | 4.75х |
Как можно заметить, что-то тут не сходится. GPU-ускорение должно быть по определению заметно быстрее в обработке видеоматериала, однако, мы получили результат лишь слегка быстрее, чем у процессора с интегрированной графикой. Как так?
При этом, если посмотреть на мониторинг nvtop, дискретная графика явно была нагружена.
Хорошо, один раз что-то пошло не так, но впереди ещё 4 теста. На всякий случай с помощью инструмента prime-run дополнительно укажем использование непосредственно дискретной графики.
```bash
prime-run ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```
И мы получаем… 4.77?
Номер теста | Скорость |
2 | 4.77х |
My honest reaction:
Так, ну хорошо, допустим, что и prime-run недостаточно, хотя nvtop опять же показал, что GPU был загружен. Перейдём к тяжёлой артиллерии и с помощью EnvyControl заставим систему использовать только дискретную графику. Заодно по новой проведём тест процессора, которому iGPU, надеюсь, уже не сможет помочь сжульничать против старичка Xeon.
```bash
sudo envycontrol -s nvidia --force-comp --coolbits 24
reboot
```
Чудесно, теперь у нас чёрный экран и на Wayland сессии, и на X11.
То FFmpeg удивляет, то теперь EnvyControl, до этого эксперимента работавший стабильно.
Et tu, Brute?
Хорошо, идём в TTY на Ctrl+Alt+F3, вводим логин, пароль и возвращаем всё назад через EnvyControl:
```bash
sudo envycontrol --reset
``
И, Слава Богу, Wayland сессия Кедов снова запустилась, ура!
Только вот по тесту я так и не продвинулся.
Попытаюсь снова переключиться только на дискретку, но и через указание экранного менеджера SDDM:
```bash
sudo envycontrol -s nvidia --dm sddm
reboot
```
* Е[ДАННЫЕ УДАЛЕНЫ]ёт. Возможно…
Wayland вновь порадовал чёрным экраном при попытке зайти в систему, в X11же зашёл без проблем, да вот только nvtop показывает, что iGPU продолжает работать вместе с дискреткой.
Ладно, EnvyControl отчего-то не радует на текущий момент. Прибегнем к его страшному старшему кузену — OptimusManager.
Работает он только с Иксами, в отличие от EnvyControl, который ранее нормально работал и под Wayland. Пробуем:
```bash
optimus-manager --switch nvidia
``
И получаем ошибку с указанием читать логи с букетом других ошибок, от чтения которых понятнее не стало.
Ладно, видимо придётся продолжать тест дальше без переключения в режим Nvidia.
```bash
ERROR: the latest GPU setup attempt failed at Xorg pre-start hook.
Log at /var/log/optimus-manager/switch/switch-20240603T140533.log
Cannot execute command because of previous errors.
```
Продолжаем экзекуцию FFmpeg и меня в попытках понять, почему всё так, как оно есть, а не так, как казалось должно быть. Так и быть, буду дальше использовать prime-run, хотя он, кажется, ничего и не меняет.
```bash
prime-run ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```
Номер теста | Скорость |
3 | 4.78х |
4 | 4.79х |
5 | 4.81х |
Итого среднее — 4.78x.
Мда, я откровенно говоря ожидал иных результатов. В моём представлении всё должно было ускориться раза в 2 минимум. Также как это происходит в условном DaVinci Resolve, KdenLive и прочих видеоредакторах, где под капотом в бэкенде, скорее всего, также где-то зарыт FFmpeg.
В итоге же разница всего лишь 15.5% относительно Ryzen 5600H.
Ну что ж, ладно, вернёмся к удалённому серверу и запустим тест там:
```bash
ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```
Начало уже радует: обошлось без сюрпризов, и появилась хоть какая-то закономерность. Затычка для PCI-слота с 2 гигами VRAM оказалась слабее, как процессоров, так и своего более свежего собрата из RTX-линейки. Правда, разница не такая уж и впечатляющая.
У Quadro P400 — в FP32 производительность 0.64 TFLOPS. А у мобильной RTX 3050TI в зависимости от теплопакета должна быть около 8.7 TFLOPS.
Номер теста | Скорость |
1 | 2.99x |
2 | 2.99x |
3 | 2.99x |
4 | 2.99x |
5 | 2.99x |
Стабильно, даже подозрительно стабильно — 2.99x.
И при разнице в 13.59 раз по сухой производительности в TFLOPS в FFmpeg мы наблюдаем разницу всего лишь в 1.59 раз.
Различия в работе FFmpeg на CPU и GPU
От практики перейдём теперь к теории, как это всё по идее должно работать.
Обещания теории, что GPU должны быть заметно быстрее, упоминать как-то странно. На примере двух разных систем убедились, что по крайней мере в их случае — это не так.
Судя по тому, как редко кто-то вспоминает про GPU-ускорение в FFmpeg, и когда до этого всё-таки доходит, то в гугле видны другие, такие же несчастные, у которых GPU оказался едва быстрее, либо вовсе медленнее.
Процесс работы на процессоре
При транскодировании на процессоре FFmpeg выполняет следующие действия:
Разделение контейнера на отдельные потоки (аудио, видео).
Декодирование потоков в их сырые форматы.
Применение фильтров к этим потокам (например, масштабирование до 720p для уменьшения размера файла).
Кодирование потоков в указанные форматы.
Мультиплексирование потоков обратно в один файл.
Процесс работы на Видеокарте
NVIDIA Video Codec SDK
Когда мы включаем аппаратное ускорение, некоторые из этих шагов могут выполняться на GPU:
Декодирование потоков происходит на NVDEC (Nvidia Video Decoder).
Фильтрация (например, масштабирование) может выполняться на GPU с использованием CUDA.
Кодирование потоков осуществляется на NVENC (Nvidia Video Encoder).
При этом аудиопотоки всё равно обрабатываются на CPU, так как NVENC/NVDEC предназначены только для видео.
После декодирования сырые видеокадры отправляются в VRAM для ускоренной фильтрации.
После фильтрации кадры кодируются и возвращаются в основную память системы для мультиплексирования и завершения процесса.
Закрадывающиеся сомнения
В моих тестах явно есть ошибка…
Может быть со злосчастным видео от Blender Foundation что-то не так — надо бы посмотреть, какой у него вообще кодек.
```bash
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 bbb_sunflower_2160p_30fps_normal.mp4
```
Используется h264. Так, h264, ну, всё верно, норма для mpeg4 контейн… стоп.
Контейнер у нас .mp4, что и расшифровывается как mpeg4, но кодеки то у нас h264/h265/266, либо софтверный libx264 и т.д.
Так, а у меня?
*приходит осознание что сам себя поймал в ловушку Джокера*
Чёрт, а я по привычке везде проставил mpeg4, привыкнув считать его синонимичным с контейнером для видео .mp4 в целом.
Хотя, mpeg4 в документации Nvidia за 2023 всё равно указан как также аппаратно ускоряемый, в отличие от приведённой мною выше схемы в разделе »Процесс работы на Видеокарте».
Впрочем, на тормознутости обработки старичка mpeg4, вероятно, сказывается, что он, будучи из 1999 года, сам по себе был менее эффективен в сжатии информации. А также, вероятно, обделён вниманием и со стороны самой Nvidia, так как на его замену есть более эффективные программные и аппаратные кодеки, приходящиеся ему потомками.
Давай по новой
Продолжаем сизифов труд и попробуем теперь прогнать с нормальным кодеком (h265) тесты на ноуте и тестовом сервере.
Заодно в процессе чтения документации я понял, что масштабирование разрешения у меня проходило на CPU, а можно было также задействовать CUDA.
```bash
ffmpeg-cuda -hwaccel cuda -hwaccel_output_format cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale_cuda=1920:1080" -c:v hevc_nvenc -preset medium output_cuda.mp4
``
RTX 3050Ti:
Номер теста | Скорость |
1 | 6.59x |
2 | 6.59x |
3 | 6.57x |
4 | 6.56x |
5 | 6.56x |
— Среднее: 6.576x
-Разница h265 vs mpeg 4: +37.5%
-Разница с Ryzen 5600H: +297%
— Разница с Quadro P400: +245.7%
Quadro P400:
Номер теста | Скорость |
1 | 2.68x |
2 | 2.68x |
3 | 2.67x |
4 | 2.67x |
5 | 2.67x |
— Среднее: 2.676x
-Разница h265 vs mpeg 4: -11.7%
-Разница с Ryzen 5600H: +92.5%
-Разница с RTX 3050Ti: -245.7%
```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale=1920:1080" -c:v libx265 -preset medium output_libx265.mp4
```
Ryzen 5600H:
Номер теста | Скорость |
1 | 1.4x |
2 | 1.37x |
3 | 1.42x |
4 | 1.35x |
5 | 1.41x |
— Среднее: 1.39
-Разница h265 vs mpeg 4: -297%
-Разница с Quadro P400: -92.5%
-Разница с RTX 3050Ti: -473%
Xeon 2630:
К сожалению, наш билд FFmpeg не имеет поддержки libx265/264, так что Xeon выбывает.
Результаты уже больше похожи на правду. Но и без аномалий не обошлось — Quardo P400 в h265 внезапно показала себя на 11.7% хуже.
Интересно, а как себя относительно ноутбучного железа покажет не престарелое железо разряда мечта сборщика БУ компов «для учёбы», а что-то помощнее из серверного сегмента? Давайте попробуем?!
Рубрика «Эксперименты», зашла слишком далеко. Проснитесь, мр. Фримэн.
Тестовый сервер 2 — AlmaLinux 9.4, ядро 6.6.31–1.el9.x86_64, 7 гигов DDR4 и 2 гига swap.
CPU — EPYC 7551P, 32 ядра с отключенным Hyper-threading, GPU — RTX A2000.
Версия драйвера Nvidia — 555.42.02, 6 гигов VRAM, FFmpeg — свой билд с оптимизациями под CUDA.
В FP32 у мобильной RTX 3050Ti и RTX A2000 должен быть примерно одинаковый результат. Разве что видеопамяти у A2000 побольше — 6 гигов против 4.
```bash
ffmpeg-cuda -hwaccel cuda -hwaccel_output_format cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale_cuda=1920:1080" -c:v hevc_nvenc -preset medium output_cuda.mp4
``
RTX A2000:
Номер теста | Скорость |
1 | 6.24x |
2 | 6.81x |
3 | 6.8x |
4 | 6.79x |
5 | 6.76x |
— Среднее: 6.68x
-Разница RTX 3050Ti: +1.58%
— Разница с Ryzen 5600H: +6.43%
-Разница с Xeon 2630: +89%
-Разница с Quadro P400: +249%
— Разница с EPYC 7551P: +237%
В итоге получаем схожий результат в пределах погрешности. В этот раз Терафлопсы соотнеслись с реальностью, а вот с EPYC есть загвоздка: AMD AMF на сервере нет, а без него Эпики в аппаратное ускорение не умеют и аналогов libx264/265 не имеют.
Ну что ж, тогда попробуем снова старичка mpeg4.
```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf scale=1920:1080 -c:v mpeg4 -preset medium output_no_acc.mp4
```
EPYC 7551P:
Номер теста | Скорость |
1 | 2.89x |
2 | 2.79x |
3 | 2.83x |
4 | 2.77x |
5 | 2.81x |
— Среднее: 2.818x
-Разница RTX 3050Ti: -301%
-Разница с Ryzen 5600H: -89.8%
-Разница с Xeon 2630: -62%
-Разница с Quadro P400: +5.3%
-Разница с A2000: +57%
```bash
ffmpeg-cuda -hwaccel cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium output_no_cuda_scale.mp4
```
И на GPU —
RTX A2000:
Номер теста | Скорость |
1 | 1.76x |
2 | 1.81x |
3 | 1.8x |
4 | 1.79x |
5 | 1.77x |
— Среднее: 1.786
-Разница RTX 3050Ti: -267%
-Разница с Ryzen 5600H: -231%
-Разница с Xeon 2630: -97.8%
-Разница с Quadro P400: -67.4%
-Разница с EPYC 7551P: -57%
AMD, конечно, умеет удивлять, но нередко любит и разочаровывать. Иного результата я ожидал от 32 ядер. Видимо Эпики под работу с мультимедиа контентом совсем не заточены. И смею предположить, подозрительно хороший результат Ryzen5600H связан был с использованием iGPU.
Впрочем, и с Nvidia не обошлось без мистики и странностей — с mpeg4 A2000 справилась куда хуже, чем RTX 3050Ti. По производительности они почти идентичны, так что предположу, что разрыв вызван разными версиями ПО на моём ноутбуке и тестовом сервере.
Теперь в h264
Для полноты эксперимента заодно посмотрим, изменится ли что-то, если сохранить кодек как и у оригинального видео — h264?
```bash
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale_cuda=1920:1080" -c:v h264_nvenc -preset medium output_cuda.mp4
```
RTX A2000:
Номер теста | Скорость |
1 | 6.24x |
2 | 6.79x |
3 | 6.81x |
4 | 6.8x |
5 | 6.8x |
— Среднее: 6.68x
— Разница c RTX A2000(H265): 0%
Quadro P400:
Номер теста | Скорость |
1 | 2.66x |
2 | 2.66x |
3 | 2.66x |
4 | 2.66x |
5 | 2.66x |
— Среднее: 2.66x
— Разница c Quadro P400(H265): -12.4%
RTX 3050Ti:
Номер теста | Скорость |
1 | 6.66x |
2 | 6.66x |
3 | 6.69x |
4 | 6.65x |
5 | 6.66x |
— Среднее: 6.66x
— Разница c RTX 3050Ti (H265): +1.27%
```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale=1920:1080" -c:v libx264 -preset medium output_cpu.mp4
```
Ryzen 5600H:
Номер теста | Скорость |
1 | 2.49x |
2 | 2.48x |
3 | 2.47 |
4 | 2.46 |
5 | 2.48 |
— Среднее: 2.476x
— Разница c Ryzen5600H (H265): +78.12%
Результаты, с одной стороны, закономерные, а с другой — снова удивляющие. RTX A2000 не показала вообще изменений между H264 и H265, у RTX 3050Ti в пределах погрешности, Quadro P400 внезапно показала себя заметно хуже с H264, а Ryzen 5600H наоборот — намного лучше справился с H264.
Итоговая таблица
Было
Тестовая конфигурация | Изначальный кодек | Конечный кодек | Среднее |
Ryzen 5600H | h264 | mpeg4 | 4.138x |
RTX 3050Ti | h264 | mpeg4 | 4.78x |
Xeon 2630 | h264 | mpeg4 | 3.534x |
Quadro P400 | h264 | mpeg4 | 2.99x |
EPYC 7551P | h264 | mpeg4 | 2.818x |
RTX A2000 | h264 | mpeg4 | 1.786x |
Стало (H265)
Тестовая конфигурация | Изначальный кодек | Конечный кодек | Среднее |
RTX A2000 | h264 | h265 | 6.68x |
RTX 3050Ti | h264 | h265 | 6.576x |
Quadro P400 | h264 | h265 | 2.676x |
Ryzen 5600H | h264 | h265 | 1.39x |
Стало (H264)
Тестовая конфигурация | Изначальный кодек | Конечный кодек | Среднее |
RTX A2000 | h264 | h264 | 6.68x |
RTX 3050Ti | h264 | h264 | 6.66x |
Quadro P400 | h264 | h264 | 2.66x |
Ryzen 5600H | h264 | h264 | 2.476x |
Выводы
Я знаю, что ничего не знаю. © Возможно, Сократ, но и это — не точно.
Скелетор Я же вернусь в следующей серии, где, возможно, снова затронем FFmpeg с транскодированием. Либо с уже новой, менее болезненной к разбору темой.
Спасибо за прочтение, с нетерпением жду ваших комментариев!