Сколько потоков ffmpeg влезет в один сервер: считаем транскодинг по ядрам и памяти
«Возьмём сервер на 16 ядер, будем гнать транскодинг пачками» — план выглядит разумно, пока кто-то не спросит: пачками по сколько? Ответ «попробуем и посмотрим» работает, но стоит денег и времени на подбор, а неправильная оценка в обе стороны ощутима — либо сервер простаивает вдвое дороже нужного, либо очередь транскодинга не успевает за потоком входящих файлов и растёт бесконечно. Транскодинг — редкий случай, когда узкое место почти всегда предсказуемо заранее: это CPU и память, а не диск и не сеть. Разберём, как посчитать реальную ёмкость сервера под параллельный ffmpeg-транскодинг, не гадая и не тестируя вслепую.
Содержание
- Почему транскодинг упирается в CPU, а не в диск и сеть
- Многопоточность внутри задачи: что реально делает -threads
- Одна задача с максимумом потоков против нескольких параллельных
- Память: буферы кадров, а не файлы целиком
- Аппаратное ускорение: VAAPI и NVENC снимают нагрузку с CPU
- Практическая методика: считаем ёмкость под конкретный поток видео
Почему транскодинг упирается в CPU, а не в диск и сеть
Транскодинг видео — это декодирование входного потока в кадры, применение фильтров (масштабирование, деинтерлейс, цветокоррекция) и повторное кодирование в целевой кодек. Все три этапа — чистые вычисления над массивами пикселей: поиск похожих блоков между кадрами, дискретное косинусное преобразование, энтропийное кодирование. Диск в этой цепочке участвует дважды — прочитать исходный файл и записать результат, — и для типичного видео это единицы-десятки мегабайт в секунду. Даже на медленном сетевом хранилище это не идёт ни в какое сравнение с тем, что ffmpeg способен наверстать за счёт буферизации чтения вперёд.
Отсюда практическое следствие: если вы видите, что ffmpeg упирается в диск или сеть, а не в CPU — почти наверняка проблема не в видео, а в конфигурации: файл читается через медленный сетевой том без кэша, или вывод пишется синхронно на перегруженный диск с другими задачами. В норме top или mpstat во время транскодинга должны показывать процессы ffmpeg, потребляющие сотни процентов CPU (в пересчёте на многопоточность), а iostat — почти простаивающий диск. Если картина обратная, чинить нужно ввод-вывод, а не переоценивать число ядер.
Это упрощает планирование ёмкости по сравнению, скажем, с веб-сервисом, где узкое место прыгает между CPU, диском, сетью и блокировками базы. Здесь один явный лимитирующий ресурс — вычисления, — и второй, менее очевидный, но не менее жёсткий — оперативная память под буферы кадров. Про неё дальше отдельно.
Многопоточность внутри задачи: что реально делает -threads
Кодировщики вроде libx264 и libx265 параллелят работу не построчно и не «сами по себе», а по конкретным механизмам, у которых есть предел эффективности. У x264 это в первую очередь frame-level parallelism (несколько кадров кодируются одновременно на разных стадиях конвейера) и slice-based parallelism (кадр режется на горизонтальные полосы, которые кодируются параллельно, но с потерей части эффективности сжатия на границах полос). Флаг -threads N задаёт число рабочих потоков; -threads 0 (или отсутствие флага в современных сборках) отдаёт кодировщику право определить число потоков автоматически по числу ядер.
ffmpeg -i input.mp4 -c:v libx264 -preset medium -threads 4 -c:a aac -b:a 128k output.mp4
Ключевой нюанс: рост числа потоков внутри одной задачи ffmpeg даёт отдачу не линейно. До определённого числа ядер (для 1080p это обычно 4-8, для 4K больше — но точная цифра зависит от контента, preset и кодека, замеряйте сами) прирост скорости заметный. Дальше накладные расходы на синхронизацию между потоками, конкуренция за общий кэш процессора и падение эффективности сжатия от лишних срезов кадра начинают съедать выгоду. Отдать одной задаче все 32 ядра сервера почти всегда хуже по суммарной пропускной способности, чем разделить эти же 32 ядра между несколькими параллельными задачами транскодинга — механика та же, что разобрана в статье про точку перегиба числа потоков: после какого-то числа воркеров каждый дополнительный не ускоряет работу, а замедляет всех.
Практический вывод: не гонитесь за максимальным -threads на одну задачу. Найдите точку, где прирост скорости одной задачи от добавления потока падает ниже, условно, 10-15% — и используйте это число как размер «слота» для одной параллельной задачи, а остальные ядра отдайте под соседние задачи.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОдна задача с максимумом потоков против нескольких параллельных
Здесь есть развилка, которую стоит явно проговорить, потому что интуиция часто подсказывает неверный ответ.
Сценарий A — минимизировать задержку одного файла: клиент ждёт конкретное видео прямо сейчас (live-транскодинг, превью для загрузки), и важна скорость обработки именно этого файла. Тогда есть смысл отдать задаче больше потоков, даже с потерей части эффективности на синхронизации — потому что метрика успеха «как быстро готов этот файл», а не «сколько файлов в час обработает сервер».
Сценарий B — максимизировать пропускную способность очереди: есть пачка файлов, и важно суммарное число обработанных файлов в час, а не время каждого конкретного. Тогда правильнее взять умеренное -threads на задачу (в районе точки, где эффективность многопоточности ещё высокая) и запускать несколько процессов ffmpeg параллельно — через очередь задач (systemd-таймеры, cron, самописный воркер-пул, или готовые инструменты вроде очередей на Redis/RabbitMQ перед пулом воркеров).
Таблица ориентировочно показывает разницу подходов на сервере с 16 физическими ядрами:
| Подход | Потоков на задачу | Задач одновременно | Что оптимизируется |
|---|---|---|---|
| Максимум потоков на задачу | 16 | 1 | время готовности конкретного файла |
| Умеренная многопоточность | 4 | 4 | суммарная пропускная способность |
| Минимальная многопоточность | 1-2 | 8-12 | максимум одновременных сессий (live-стриминг с низким разрешением) |
Для батч-обработки (конвертация архива, генерация превью, подготовка VOD) почти всегда выгоднее сценарий B. Для интерактивных сценариев, где пользователь ждёт результат — ближе к A, но с оговоркой: если таких интерактивных запросов много одновременно, вы всё равно возвращаетесь к задаче распределения ядер между параллельными задачами, просто с более жёстким требованием к задержке каждой.
Если задачи запускаются в контейнерах, не забывайте про cgroup-лимиты CPU — процесс ffmpeg внутри контейнера с --cpus=2 не получит больше двух ядер, даже если -threads попросит больше; про то, как считать лимиты CPU и памяти для контейнеров, подробно в статье про ресурсы Docker.
Память: буферы кадров, а не файлы целиком
Диск читает и пишет файл потоково, но в оперативной памяти в любой момент времени держится не весь файл, а несколько декодированных кадров — и это первое, что стоит понять про память при транскодинге: она не зависит от размера файла на диске, она зависит от разрешения кадра и глубины буферизации кодировщика.
Один несжатый кадр в формате YUV420 (стандартная цветовая субдискретизация для видео) занимает примерно ширина × высота × 1.5 байт. Для 1080p (1920×1080) это около 3 МБ на кадр, для 4K (3840×2160) — около 12 МБ. Кодировщик держит в памяти не один кадр, а сразу несколько: буфер входных кадров, окно поиска референсных кадров для B/P-фреймов, буфер lookahead (кодировщик x264/x265 с включённым lookahead заглядывает на десятки кадров вперёд, чтобы принять решения о распределении битрейта), и выходной буфер. Это ориентировочная методика оценки, а не точная формула — конкретное число зависит от preset, GOP-структуры и настроек lookahead, и его стоит подтверждать замером, а не расчётом на бумаге.
Практический способ узнать реальное потребление одной задачи — замерить, а не вычислять теоретически:
ffmpeg -i input.mp4 -c:v libx264 -preset medium output.mp4 &
PID=$!
watch -n 1 "grep VmRSS /proc/$PID/status"
или через ps:
ps -o pid,%cpu,rss,cmd -C ffmpeg
RSS в килобайтах — это резидентная память процесса на текущий момент, её и умножайте на планируемое число параллельных задач с запасом 20-30% на пики (лишний буфер фильтров, аудиодорожка, метаданные). Для 1080p с типичными настройками ориентируйтесь на диапазон в несколько сотен мегабайт на задачу, для 4K — существенно больше, в разы; точную цифру даст только замер на реальном файле, потому что разброс между «лёгким» и «тяжёлым» с точки зрения кодировщика видео (движение, шум, сложные текстуры) заметный.
Отдельно проверьте, не участвуют ли фильтры вроде scale, yadif (деинтерлейс) или overlay — каждый добавляет свой буфер кадров в конвейер, и цепочка из нескольких фильтров может удвоить-утроить память одной задачи по сравнению с прямым перекодированием без фильтров.
Аппаратное ускорение: VAAPI и NVENC снимают нагрузку с CPU
Если расчёт по ядрам упирается в потолок раньше, чем хотелось бы, аппаратное ускорение — способ сдвинуть узкое место с CPU на специализированный блок кодирования/декодирования внутри GPU. Логика та же, что и в других задачах, где выделенная под конкретную операцию схема обгоняет процессор общего назначения: свой участок кристалла жёстко заточен под ограниченный набор операций и не тратит такты на универсальность.
Два основных пути на Linux-сервере:
VAAPI (Video Acceleration API) — открытый интерфейс, который на серверах с процессорами Intel даёт доступ к встроенному блоку Quick Sync (при условии, что он физически присутствует и не отключён в BIOS/гипервизоре — на многих серверных Xeon его нет вовсе, это фича скорее настольных и части мобильных линеек Intel). Проверка доступности:
vainfo
ls /dev/dri/
Пример перекодирования с VAAPI:
ffmpeg -hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i input.mp4 \
-vf 'format=nv12,hwupload' -c:v h264_vaapi -b:v 4M output.mp4
NVENC — блок аппаратного кодирования на видеокартах Nvidia, доступен на серверах с проброшенной или интегрированной GPU. Требует установленного драйвера Nvidia и сборки ffmpeg с поддержкой nvenc:
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p4 -b:v 4M output.mp4
Важные оговорки, честно: аппаратные кодировщики почти всегда проигрывают программным (libx264/libx265 с хорошим preset) по качеству на тот же битрейт — разница ощутима на низких битрейтах и почти незаметна на высоких. У части линеек видеокарт Nvidia драйвер ограничивает число одновременных NVENC-сессий на одной карте — конкретное число зависит от модели и версии драйвера, уточняйте для своего железа. И декодирование с фильтрацией всё равно частично идут через CPU, если явно не перевести всю цепочку на аппаратные буферы (hwaccel_output_format) — аппаратное ускорение снимает нагрузку с CPU, но не обнуляет её полностью.
Если ваш сервер — виртуальная машина без физического GPU или без проброшенного /dev/dri, аппаратное ускорение просто недоступно, и весь расчёт возвращается к CPU-ядрам и памяти из предыдущих разделов.
Практическая методика: считаем ёмкость под конкретный поток видео
Собираем всё в пошаговую процедуру, которую можно повторить для своего контента, не полагаясь на чужие цифры.
Шаг 1. Возьмите репрезентативный файл. Не самый лёгкий и не самый тяжёлый из тех, что реально будете обрабатывать — усреднённый по разрешению, битрейту исходника и сложности сцены (движение, шум).
Шаг 2. Зафиксируйте целевые настройки кодирования — кодек, preset, целевой битрейт или CRF, фильтры. Разные preset дают разное потребление CPU при том же кодеке: ultrafast заметно легче slow, но хуже жмёт — это прямой компромисс между скоростью и итоговым размером/качеством файла, выбирайте осознанно под задачу.
Шаг 3. Замерьте одну задачу изолированно.
nproc
mpstat -P ALL 1
Запустите транскодинг файла из шага 1 с настройками из шага 2, зафиксируйте среднюю загрузку CPU процессом (через top или ps -o %cpu -C ffmpeg) и пиковую резидентную память (VmRSS).
Шаг 4. Посчитайте два независимых потолка.
N_cpu = floor(суммарное_число_vCPU / CPU_на_задачу_в_ядрах)
N_ram = floor(доступная_RAM_с_запасом / RAM_на_задачу)
N_итог = min(N_cpu, N_ram)
Оставьте резерв 15-20% CPU и памяти под ОС, мониторинг, сетевой стек и всплески — не планируйте впритык к 100%. Если сервер также раздаёт готовые файлы (веб-сервер, стриминг-сервис), учтите и его долю ресурсов отдельно.
Шаг 5. Проверьте под реальной параллельной нагрузкой. Расчёт по одной задаче даёт первое приближение, но не учитывает конкуренцию за общий кэш процессора и шину памяти при полной загрузке всех ядер одновременно — на практике фактическая пропускная способность при N параллельных задачах обычно на 5-15% ниже, чем N × производительность одной изолированной задачи, из-за этой конкуренции. Запустите N_итог задач одновременно на тестовом наборе файлов и сверьте суммарное время с ожиданием.
Шаг 6. Если доступно аппаратное ускорение, посчитайте отдельный потолок для него — здесь ограничение обычно не число ядер CPU, а число сессий кодировщика GPU и пропускная способность видеопамяти, и складывать эти два потолка (CPU-путь и GPU-путь) можно, если вы явно распределяете задачи между ними.
Эта методика по духу близка к расчёту параллелизма CI-раннера — там тоже несколько независимых потолков (CPU, RAM, диск, сеть), и превышение любого роняет разом все параллельные задачи, а не деградирует плавно; подробный разбор такого расчёта — в статье про предел параллельных сборок. Если вы уже понимаете, что нагрузка предполагает специализированный сервер под видеообработку с самого начала — конфигурации под такие задачи разобраны в статье про сервер для видеомонтажа и рендер-фермы.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Стоит ли ставить -threads в максимум числа ядер сервера для каждой задачи?
Обычно нет, если задач планируется несколько параллельно. Выше определённого числа потоков (зависит от контента и preset, замеряйте сами) прирост скорости одной задачи падает, а конкуренция за кэш и память между несколькими такими «прожорливыми» задачами снижает суммарную пропускную способность сильнее, чем экономия от максимальной многопоточности одной.
Почему сервер с большим запасом RAM всё равно упирается в память при транскодинге нескольких файлов одновременно?
Потому что память тратится не на хранение файла, а на буферы кадров кодировщика — для 4K-контента и агрессивного lookahead один процесс ffmpeg может держать заметно больше памяти, чем ожидается интуитивно от «просто перекодирования видео». Замеряйте RSS процесса, а не оценивайте на глаз по размеру файла.
NVENC или VAAPI однозначно лучше программного кодирования?
Не однозначно. Аппаратные кодировщики быстрее и легче для CPU, но обычно проигрывают в эффективности сжатия на тот же битрейт — итоговый файл либо крупнее, либо заметнее по артефактам при том же размере. Выбор зависит от того, что важнее для задачи: скорость и низкая нагрузка на CPU или максимальное качество на минимальном битрейте.
Как понять, что сервер уже упёрся в лимит, а не просто временно нагружен?
Смотрите на устойчивую загрузку CPU близко к 100% на всех ядрах при растущей очереди задач, а не на кратковременные пики. Если очередь стабильно растёт при постоянном входящем потоке — вы выше найденного потолка N_итог, и нужен либо более мощный сервер, либо разгрузка через аппаратное ускорение.
Можно ли использовать эту методику для live-транскодинга (стриминг в реальном времени), а не батч-обработки?
Да, с поправкой: для live критична не только пропускная способность, но и стабильная задержка каждой сессии — здесь лучше закладывать больший запас по CPU на сессию (ближе к сценарию A из раздела про параллельные задачи) и тестировать не средней, а пиковой нагрузкой, потому что просадка ниже реального времени в live недопустима, в отличие от батч-очереди, где временная задержка обработки не критична.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →