Сравнение кодеков JPEG2000 на видеокарте: fvJPEG2000 и nvJPEG2000
Обновлено:
Это сравнение двух кодеков JPEG2000 на видеокарте: Fastvideo JPEG2000 и NVIDIA nvJPEG2000, на одной RTX 4090 и на сжатых файлах одинакового размера. При кодировании fvJPEG2000 быстрее в 3,9–6,6 раза; при декодировании кодеки идут вровень — расхождение не больше 8 % и в обе стороны. Ниже методика измерений, полные таблицы по кадрам 2K и 4K, разбор профилировщиком и всё, что нужно, чтобы повторить измерения у себя.

Слева кодер fvJPEG2000 быстрее в 3,9–6,6 раза, справа декодеры идут вровень.
Итоговые результаты: кодеки fvJPEG2000 и nvJPEG2000 на RTX 4090
Видеокарта RTX 4090, одинаковый размер сжатого файла, по три повтора в каждой точке. Основные результаты получены одним прогоном: скорость, энергия и проверки качества. Отдельными прогонами измерены структурные меры качества (раздел 9), режим PCRD (раздел 10) и память видеокарты (раздел 11).
В кодировании быстрее fvJPEG2000, причём с большим запасом. 1914 кадров в секунду против 292 на 2K с потерями, 616 против 160 на 4K. Разрыв от 3,9 до 6,6 раза в зависимости от кадра и режима. Это и есть типовая задача: поток данных надо сжимать максимально быстро, и тут всё упирается в производительность кодера.
В декодировании кодеки идут вровень. 1024 кадра в секунду у fvJPEG2000 против 1033 на 2K с потерями и 436 против 438 на 2K без потерь: разница менее процента, и оба раза в пользу nvJPEG2000. На 4K расхождение больше и уже в обе стороны: и там и там 8 %, только с потерями впереди nvJPEG2000, 428 против 394, а без потерь впереди fvJPEG2000, 145 против 134. Но достаётся это равенство по-разному: на трёх задачах из четырёх лучшая точка fvJPEG2000 занимает от 26 до 29 логических ядер процессора, а nvJPEG2000 — от 3,4 до 4,8 (раздел 11).
Латентность кодека. В режиме одиночных кадров, когда важно время обработки одного кадра, а не скорость обработки потока кадров, кодер fvJPEG2000 быстрее в 1,5–2,5 раза: 2,6 мс против 5,1 мс на кадре 2K с потерями. При декодировании одиночного кадра впереди nvJPEG2000, в 1,5–2,1 раза.
Ниже о том, как это измерено, почему получается именно так и как повторить у себя. Если нужен только вывод — что выбрать под свою задачу, — он собран в разделе 12.
С чем сравнивается fvJPEG2000: nvJPEG2000 при схеме запуска Fastvideo, а не штатный пример NVIDIA
Кодек NVIDIA nvJPEG2000 для тестирования взят не в том виде, в каком его получают "из коробки". Штатный пример из набора CUDALibrarySamples обрабатывает кадры по одному: одно состояние кодека, одна очередь заданий на видеокарте, синхронизация после каждого кадра. В этой статье библиотека nvJPEG2000 запускается иначе — несколько потоков процессора, в каждом потоке несколько кадров в работе одновременно, лучшее сочетание найдено перебором (раздел 4.3). Это схема запуска Fastvideo, и она применена к библиотеке NVIDIA так же, как и к кодеку fvJPEG2000.
Режим одиночных кадров в таблицах разделов 6 и 7 — это и есть то, что делает штатный пример NVIDIA. Проверено прямым измерением: при одинаковой схеме подачи кадров штатный пример и программа измерений Fastvideo расходятся на 1–3 %, а сжатые файлы совпадают (приложение).
| Ускорение nvJPEG2000 при схеме запуска Fastvideo |
Кодирование | Декодирование |
|---|---|---|
| 2K, с потерями | 1,47× | 3,47× |
| 2K, без потерь | 1,28× | 1,85× |
| 4K, с потерями | 1,25× | 2,22× |
| 4K, без потерь | 1,14× | 1,47× |
В таблице показано, во сколько раз лучшее сочетание потоков и пачки быстрее режима одиночных кадров у библиотеки NVIDIA. Обе величины измерены программой Fastvideo, на одной карте, на одних файлах, и разница между ними — только способ запуска.
Важно не забывать, что в режиме одиночных кадров время измеряется без учёта переноса кадров через шину, а лучшее сочетание — вместе с этим переносом (раздел 4.2). Значит прибавка от схемы запуска здесь скорее занижена, чем завышена: одиночному режиму зачтено меньше работы, чем он делает на самом деле.
Что это меняет в сравнении двух кодеков. При кодировании отрыв fvJPEG2000 — от 3,9 до 6,6 раза; если бы сравнение шло со штатным примером NVIDIA, отрыв был бы от 4,8 до 9,7 раза. При декодировании разница ещё заметнее: в разделе 7 кодеки идут вровень, а против штатного примера fvJPEG2000 был бы впереди в 1,6–3,4 раза. То есть отрыв, показанный в этой статье, посчитан по сравнению с лучшей реализацией, которую удалось получить из nvJPEG2000, а не по сравнению с обычным способом её использования.
1. Какие кодеки JPEG2000 сравниваются и зачем
В настоящее время существует несколько реализаций кодека JPEG2000 (в обиходе J2K) на графическом процессоре, как коммерческих, так и открытых. Эта статья сравнивает два кодека, из которых инженеру приходится выбирать чаще всего.
Первый кодек — из библиотеки nvJPEG2000 компании NVIDIA. Библиотека бесплатна, но поставляется отдельно от CUDA Toolkit: её можно скачать с сайта NVIDIA или установить из пакета для Python. Дальше в тексте этот кодек называется полным именем, в таблицах сокращается до NV.
Второй — кодек JPEG2000 компании Fastvideo, далее fvJPEG2000, в таблицах FV. Он поставляется в составе Fastvideo SDK и лицензируется на коммерческой основе.
Оба кодека написаны на CUDA и работают на видеокартах NVIDIA. Смысл статьи в сравнительном анализе: какие результаты дают две разные реализации одного стандарта на одном и том же компьютере. Компьютер важен целиком, а не только видеокарта, так как часть работы кодека идёт на процессоре. Методика сравнения и параметры кодеков приведены со всеми подробностями, нужными для повторения.
Почему сравниваются именно эти два кодека?
Есть и другие кодеки, но мы их здесь не тестировали. Процедура измерения производительности здесь опубликована полностью, и у кого есть лицензия на эти продукты, тот может провести такие же тесты сам и опубликовать свои результаты.
Есть и OpenJPEG — открытая реализация этого алгоритма, но она работает на центральном процессоре, и разница в производительности с видеокартой очень значительна. Этот кодек имеет смысл тестировать отдельно.
Первый вопрос любого инженера звучит просто: зачем платить, если у NVIDIA уже есть бесплатное решение? На такой вопрос картинками и обещаниями не отвечают: нужны измерения производительности, которые каждый может повторить у себя, на своей видеокарте и на своих изображениях. Эта статья про то, как такие измерения организовать, и что получилось, когда мы их провели.
Идея статьи в одной фразе
Главное здесь не результаты производительности, а методика их получения. Сами результаты могут устаревать с каждой новой версией драйвера, библиотеки и видеокарты; процедура, по которой они получены, живёт заметно дольше. Поэтому статья построена так, что её можно читать как инструкцию: как привести два разных кодека к сопоставимым условиям, в каких режимах можно измерять скорость и почему этих режимов как минимум четыре, что входит в измеряемое время, как убедиться, что декодер действительно восстановил изображение, а не выполнил лишь часть работы.
Из этого следуют три правила, к которым мы возвращаемся в каждом разделе:
- Сравнивать кодеки нужно по одинаковому результату, а не по одинаковому значению параметра качества. Шкалы качества у разных кодеков свои, и общей единицей измерения становится размер сжатого файла в байтах. При этом качество восстановленных изображений тоже приходится контролировать, ведь только вместе эти два условия делают сравнение корректным.
- Скорость кодирования в кадрах в секунду без указания режима работы не имеет смысла. Время обработки одного кадра и общая производительность при потоковой обработке — это разные величины, и они могут различаться в разы: у кодера fvJPEG2000 на кадре 2K переход от одиночных кадров к лучшему сочетанию потоков и пачки даёт рост в пять раз, а у декодера в семь раз.
- Одной скорости работы для сравнения недостаточно. Каждое измерение сопровождается полным циклом: изображение кодируется, декодируется и сравнивается с исходным.
Если после чтения останется только это, статья свою задачу выполнила, даже если конкретные результаты к тому времени успеют измениться.
Эта работа является первой частью более широкой темы. Планы по ней собраны в разделе 16, а открытый проект, где используется эта методика и код, описан в разделе 14.
2. Исходные изображения: кадры 2K и 4K для теста кодеков JPEG2000
Первое правило требует сравнения при одинаковом результате. Результат целиком зависит от того, что подали на вход, поэтому начинать лучше с картинок.
Измерения идут на двух изображениях, которые лежат в открытом доступе и используются в публичных бенчмарках JPEG2000. Файлы можно скачать и проверить у себя: 2k_wild.ppm и 4k_wild.ppm. Это обычные фотографические сцены с широким диапазоном детализации: и гладкие области, и мелкая фактура. Такой материал важен, потому что степень сжатия при заданном качестве целиком определяется содержимым кадра.
| Файл | 2k_wild.ppm |
4k_wild.ppm |
|---|---|---|
| Разрешение | 1920 × 1080 | 3840 × 2160 |
| Каналы | 3 | 3 |
| Разрядность | 8 бит | 8 бит |
| Размер, МБ | 5,93 | 23,73 |
Формат PPM выбран сознательно: это несжатый файл с минимальным заголовком, в котором указан формат, размеры и максимальное значение отсчёта (по сути битность), а сразу за ним идут данные изображения. Такой файл читается быстро и легко, и оба кодека получают на вход ровно одни и те же байты: никакой разницы в распаковке исходника, никакого влияния сторонних библиотек.
Стоит сказать, что такой набор даёт и чего он не даёт. Двух разрешений достаточно, чтобы увидеть главное: как поведение меняется, когда кадр перестаёт быть мелким для видеокарты. Кадр 2K не нагружает RTX 4090 целиком, кадр 4K нагружает, и это многое меняет.
Чего набор не покрывает, хотя оба кодека это умеют: разрядность данных выше 8 бит на канал. И fvJPEG2000, и nvJPEG2000 работают с данными до 16 бит на канал, и именно такими являются медицинские снимки и спутниковые кадры — те области, ради которых JPEG2000 обычно и выбирают. В этих тестах они не рассматриваются: поведение кодеков на 12 и 16 битах требует отдельного набора данных и отдельного разбора. Сюда же относятся кадры 8K и больше, многотайловые изображения и монохромные кадры.
Здесь нужна отдельная оговорка про метод. Измерения устроены как «один кадр повторяется N раз», а не «N разных кадров». Кадр 2K занимает 5,9 МБ, кадр 4K — 23,7 МБ, и оба целиком помещаются в кэш третьего уровня современного процессора. То есть исходные данные после первой итерации берутся из кэша, а не из оперативной памяти. Для оценки скорости самого алгоритма это правильно, ведь мы измеряем скорость кодека, а не подсистемы памяти, но это не то же самое, что обработка папки с разными файлами.
3. Как подбирались параметры сжатия для бенчмарков кодеков
Это очень важная часть работы, и от неё зависит, значат ли полученные результаты хоть что-нибудь.
3.1. Общий знаменатель
Два кодека умеют не одно и то же. Сравнивать имеет смысл только на тех параметрах сжатия, которые доступны каждому кодеку, и при этом все они должны быть заданы явно с обеих сторон, а не оставлены «по умолчанию»: значения по умолчанию могут быть разными.
| Параметр сжатого файла | FV | NV |
|---|---|---|
| Формат файла | JP2 | stream_type = STREAM_JP2 |
| Вейвлет, с потерями | -a irrev (CDF 9/7) | irreversible = 1 |
| Вейвлет, без потерь | -a rev (CDF 5/3) | irreversible = 0 |
| Размер кодоблока | -c 32 | code_block_w = code_block_h = 32 |
| Уровни разрешения | -l 6 | num_resolutions = 6 |
| Слои качества | 1 | num_layers = 1 |
| Порядок прогрессии | LRCP | prog_order = LRCP |
| Цветовое преобразование | включено | mct_mode = 1 |
| Субдискретизация | 4:4:4 | компоненты полного размера |
| Тайлы | выключены | enable_tiling = 0 |
| Маркеры SOP и EPH | выключены | enable_SOP/EPH_marker = 0 |
| Precincts | по умолчанию | num_precincts_init = 0 |
Четыре из этих строк — не произвольный выбор, а вынужденный, и об этом стоит сказать прямо.
Размер кодоблока 32×32. fvJPEG2000 поддерживает 16×16, 32×32 и 64×64, nvJPEG2000 — только 32 и 64, поэтому 16×16 из сравнения выпадает. Из оставшихся двух взяли 32×32: он позволяет добиться большего распараллеливания при работе на видеокарте.
Один слой качества. У nvJPEG2000 число слоёв может быть только одно: других значений интерфейс не принимает. У fvJPEG2000 в кодере слой тоже один. Значит послойное качество в сравнении не участвует.
Порядок прогрессии LRCP. Кодер fvJPEG2000 выдаёт только LRCP, декодер понимает все пять. nvJPEG2000 умеет все пять при кодировании. Общий знаменатель — LRCP.
Маркеры SOP и EPH выключены. У nvJPEG2000 они обязаны быть выключены, включить их нельзя. Соответственно они выключены и в fvJPEG2000.
Субдискретизация 4:4:4. Оба кодека поддерживают также 4:2:2 и 4:2:0, но для сравнения взят режим без потерь цветности: он не добавляет к тестам ещё одну переменную и одинаково доступен обеим сторонам.
3.2. Шкалы качества у двух кодеков разные
Здесь нужно выбрать одинаковый подход, и подходы к решению задачи у этих кодеков разные.
У fvJPEG2000 их два, и они могут работать вместе. Первый — шкала качества q от 0 до 100: она управляет квантованием, то есть тем, насколько грубо округляются коэффициенты вейвлет-преобразования. Размер файла при этом получается как следствие. Второй — режим PCRD (Post-Compression Rate-Distortion, параметр -cr): ему задают нужный коэффициент сжатия, и кодер отбрасывает младшие биты кодоблоков ровно до тех пор, пока сжатый кадр не уложится в заданный размер. Здесь всё наоборот: размер задан, а качество получается как следствие. Эти два способа можно сочетать: сначала применить квантование при заданном q, затем PCRD до заданного коэффициента сжатия.
У nvJPEG2000 такие способы: целевое отношение сигнал-шум, шаг квантования или Q-фактор в шкале 1–100. Все три говорят кодеру, насколько грубо кодировать. Цели по размеру файла или по коэффициенту сжатия среди них нет.
Общая почва, таким образом, одна — шкала качества: у fvJPEG2000 это q, а у nvJPEG2000 так же устроен Q-фактор. На этом и построено сравнение в разделах 6–9: fvJPEG2000 кодирует при q = 85, а nvJPEG2000 подбирает свой Q-фактор так, чтобы получить файл того же размера. Режим PCRD в этих измерениях выключен: у nvJPEG2000 такого режима нет вовсе. Как он влияет на скорость кодирования, измерено отдельно, в разделе 10.
Выставить качество «85» для каждого кодека и получить одинаковое сжатие нельзя: это разные шкалы, и файлы получатся разного размера. А если размеры сжатого кадра разные, то и работы у кодеков разное количество, и любое сравнение скорости обесценивается.
Как ведёт себя шкала fvJPEG2000 на этих двух снимках:
Качество q |
Сжатие 2K | Файл 2K, кБ | Сжатие 4K | Файл 4K, кБ |
|---|---|---|---|---|
| 80 | 14,2:1 | 429 | 27,7:1 | 878 |
| 83 | 11,8:1 | 517 | 22,5:1 | 1078 |
| 85 | 10,3:1 | 588 | 19,5:1 | 1246 |
| 87 | 9,1:1 | 671 | 16,8:1 | 1449 |
| 90 | 7,3:1 | 828 | 13,2:1 | 1847 |
Обратите внимание: при одном и том же значении q коэффициент сжатия у двух кадров отличается почти вдвое. Это не ошибка и не особенность кодека. Кадры здесь разные, и сравнивать их коэффициенты сжатия между собой смысла нет: степень сжатия при заданном качестве определяется содержимым кадра. Важно другое — одно и то же значение q не даёт одного и того же размера файла.
3.2.1. Размер файла — результат, а не заданная величина
Параметр качества задаёт не размер файла, а схему квантования. Кодер раскладывает изображение вейвлет-преобразованием и квантует коэффициенты тем грубее, чем ниже качество. Сколько байт из этого получится, зависит от коэффициента качества и от содержимого кадра. Размер файла здесь не параметр, а результат.
Чтобы увидеть это в чистом виде, полезно считать не коэффициент сжатия, а биты на пиксел: сколько бит в среднем нужно для кодирования одного пиксела изображения. Исходные данные — 24 бита на пиксел, т.е. по восемь бит на канал.
| Режим | 2K, бит/пиксел | 4K, бит/пиксел |
|---|---|---|
| С потерями, q 85 | 2,32 | 1,23 |
| Без потерь | 11,44 | 8,65 |
Что из этого следует на практике.
Во-первых, фраза «сжатие 20:1» ничего не значит без указания изображения. Одно и то же значение параметра качества на другом кадре даст другой коэффициент сжатия. Когда где-то сравнивают кодеки «при сжатии 20:1», первый вопрос — на чём именно проводились измерения?
Во-вторых, значение параметра качества нельзя переносить между проектами и ждать того же размера файла. Если нужен именно фиксированный размер, например чтобы уложиться в заданную полосу пропускания или в размер хранилища, нужен не параметр качества, а управление битрейтом, которое уменьшает размер сжатого кадра до нужной величины. В fvJPEG2000 это делает режим PCRD (раздел 3.2); в этом сравнении он не используется, а как он влияет на скорость, показано в разделе 10.
В-третьих, именно поэтому сравнение двух кодеков строится на совпадении размера выходного файла, а не на совпадении значения параметра качества. Иначе одна из сторон делала бы меньше работы, и разговор про скорость терял бы смысл.
3.3. Сравниваем при одинаковом размере сжатого файла
Почему нельзя сравнить два кодека при одинаковом параметре качества?
Потому что шкалы у них разные: одно и то же число в параметре качества даёт разный размер файла и разное искажение. Сравнивать надо не при одинаковом значении параметра, а при одинаковом результате, при одном и том же размере сжатого файла в байтах. От него напрямую зависит объём работы: если один кодек выдаёт файл на треть меньше, он и работает меньше, и быстрее окажется тот, кто просто сжал сильнее. Размер в байтах удобен ещё и тем, что определён однозначно: коэффициент сжатия зависит от того, что считать исходным размером, а оценка качества — от выбранной меры.
Процедура такая. fvJPEG2000 кодирует эталон при качестве 85. Дальше nvJPEG2000 подбирает свой Q-фактор делением отрезка пополам: кодирует, смотрит на размер, сдвигает границу, повторяет, пока не попадёт в цель с точностью до одной десятой процента.
Результат подбора:
| Изображение | 2K с потерями | 4K с потерями |
|---|---|---|
| Цель, байт | 601 703 | 1 275 547 |
| Найденное Q | 87,29 | 87,14 |
| Получилось, байт | 601 940 | 1 274 517 |
| Расхождение | 0,04 % | 0,08 % |
Размеры сведены с точностью лучше одной десятой процента: работы у кодеков поровну.
Насколько соответствие шкал зависит от кадра. Значения 87,29 и 87,14 очень близки, и напрашивается вывод, что шкалы качества двух кодеков переводятся друг в друга постоянным пересчётом. Это стоило проверить: если бы так и было, найденное значение можно было бы переносить с изображения на изображение.
Проверка сделана отдельно и устроена так: тот же подбор повторяется с допуском вдвое строже (0,05 % по размеру), на трёх уровнях качества и с двух разных начальных отрезков поиска: [1, 100] и [50, 99]. Второе нужно, чтобы отделить свойство кодеков от следа самой процедуры деления отрезка пополам. Из-за более строгого допуска найденные здесь значения немного отличаются от таблицы выше: для 4K при качестве 85 здесь стоит 87,17, а там 87,14.
| Качество FV | Эквивалент для 2K | Эквивалент для 4K | Разница |
|---|---|---|---|
| 80 | 74,86 | 74,77 | 0,10 |
| 85 | 87,29 | 87,17 | 0,12 |
| 90 | 94,63 | 94,56 | 0,07 |
Вывод такой: шкалы качества отличаются, но они очень похожи, а нахождение точного соответствия выходит за рамки задачи. Вполне вероятно, что оно может зависеть и от содержимого кадра. Найденное значение переносится на другое изображение как хорошее первое приближение, но подбирать под конкретный размер файла всё равно приходится заново. Именно поэтому в процедуре подбор делается для каждого изображения отдельно, а не один раз на весь набор.
Для режима без потерь подбирать нечего: там параметра качества нет вовсе, и оба кодека обязаны выдать полностью восстановимый файл. Ниже размеры сжатых файлов во всех четырёх сочетаниях условий, и там, где размер подбирался, и там, где он получился сам:
| Изображение | Файл FV, кБ | Файл NV, кБ | Сжатие |
|---|---|---|---|
| 2K с потерями | 588 | 587 | 10,3:1 |
| 4K с потерями | 1246 | 1244 | 19,5:1 |
| 2K без потерь | 2896 | 2896 | 2,1:1 |
| 4K без потерь | 8754 | 8754 | 2,8:1 |
Коэффициент компрессии порядка 2:1 для алгоритма сжатия без потерь — обычное для JPEG2000 значение на фотографическом материале, и оно совпадает с тем, что мы получаем при сжатии RAW данных.
4. Методика: скорость кодека JPEG2000 — это не одна величина
Это раздел про второе правило: скорость в кадрах в секунду без указания режима работы не имеет смысла.
Для одного и того же кодека на одной и той же видеокарте можно получить разные значения скорости. Разница не в способе измерения, а в том, как распараллелена работа. Поэтому первый вопрос при сравнении — не только «сколько кадров в секунду», но и «в каком режиме это получено».
4.1. Режимы измерений
- Режим одиночных кадров — в приложениях Fastvideo SDK это обработка одного изображения,
single image mode, однократно или с многократным повтором (ключ-repeat). Обработка следующего кадра начинается только после того, как полностью всё сделано с предыдущим, работа идёт в одном потоке, перекрытия между кадрами нет: время усредняется по тысячам повторов. Отсюда получается время обработки одного кадра. Именно эта величина нужна, когда важна скорость отклика; при большом числе повторов она повторяется в пределах процента. - Режим пачки. Несколько кадров программно объединяются в один виртуальный кадр большего размера, чтобы загрузить его в видеокарту одномоментно; на выходе получается исходное число кадров, объединение здесь виртуальное. Отдельных измерений для этого режима в статье нет: самым быстрым он не бывает, поэтому в таблицах разделов 6 и 7 пачка везде идёт вместе с потоками.
- Многопоточный режим, несколько потоков. Несколько потоков процессора, у каждого своё состояние кодека и своя очередь заданий (CUDA stream). Обработка разных кадров на видеокарте перекрывается, и скорость растёт.
- Многопоточный режим для работы с пачкой. Дополнительно несколько кадров объединяются в один виртуальный кадр большего размера, чтобы одномоментно загрузить больше данных в каждый поток обработки. Это самый быстрый режим.
Первый режим отвечает на вопрос «сколько времени необходимо для обработки одного кадра», остальные — «сколько кадров в секунду мы сможем обработать». Это разные величины, а не разная точность для одной и той же задачи: в многопоточном режиме и в режиме пачки латентность обработки одного кадра хуже, чем в одиночном, и это плата за более высокую общую производительность.
Вариант с несколькими видеокартами здесь не рассматривается: речь идёт о производительности на одной карте.
Разница в том, какая степень распараллеливания получается в каждом случае.
Насколько это важно: у fvJPEG2000 при кодировании 2K с потерями режим одиночных кадров даёт 381 кадр в секунду, а лучшее сочетание потоков и пачки — 1914, впятеро больше. У nvJPEG2000 на той же задаче 198 и 292, то есть в полтора раза. Одна видеокарта, одинаковые кадры, одинаковое сжатие, а ответ на вопрос «сколько кадров в секунду» у двух кодеков зависит от режима по-разному.
4.2. Что входит в измеряемое время, а что нет
Второй вопрос после режима работы — что именно попадает в измеряемое время.

Правило зависит от режима, и это надо назвать прямо.
В режиме одиночных кадров отсчёт идёт от данных, уже лежащих там, где их берёт кодек, до результата на другой стороне: у кодера от исходного кадра в памяти видеокарты до сжатого изображения в оперативной памяти, у декодера от сжатого изображения в оперативной памяти до восстановленного кадра в памяти видеокарты. Перенос самих пикселов через шину в отсчёт не входит ни у одной из сторон.
В многопоточном режиме отсчёт идёт от оперативной памяти до оперативной памяти в обе стороны: переносы через шину входят в измеряемое время у обеих сторон.
Почему по-разному. В режиме одиночных кадров кадры идут по одному, и кодек сам отдаёт время своей работы по стадиям, поэтому измерять можно изнутри. В многопоточном режиме работа над несколькими кадрами идёт на видеокарте одновременно, и время одной стадии одного кадра не отделить от работы над соседними. Значит границы могут быть только внешние: когда данные ушли из оперативной памяти и когда результат туда вернулся.
Какое время измеряем. Всю работу кодека, все его этапы подряд: у кодера это подготовка данных и преобразование цвета, вейвлет-преобразование, квантование и EBCOT Tier-1 на видеокарте, затем передача результата на центральный процессор и Tier-2 — формирование сжатого изображения; у декодера то же самое в обратном порядке. То, что часть работы идёт на процессоре, — не изъян измерения, а свойство JPEG2000: не все его стадии можно эффективно распараллелить. Самый тяжёлый этап, EBCOT Tier-1, считается на видеокарте, а Tier-2, сборка сжатого изображения из готовых пакетов при кодировании и разбор его структуры при декодировании, у двух кодеков устроен по-разному, и известно о нём разное.
У fvJPEG2000 Tier-2 выполняется на центральном процессоре и при кодировании, и при декодировании.
У декодера nvJPEG2000 — тоже на процессоре, и это сказано в документации NVIDIA прямым текстом: «Tier 2 decode stage (first stage of decode) is run on the CPU. All other stages of the decoding process are offloaded to the GPU» — этап Tier-2 при декодировании выполняется на центральном процессоре, все остальные этапы вынесены на видеокарту (документация nvJPEG2000). В программе это шаг nvjpeg2kStreamParse: он принимает сжатое изображение из оперативной памяти и разбирает его структуру.
Про кодер nvJPEG2000 такого однозначного утверждения в документации нет. Там сказано только, что библиотека использует для создания потока JPEG2000 и видеокарту, и центральный процессор, и что исходное изображение должно лежать в памяти видеокарты, а сжатое изображение записывается в оперативную память. Какая именно часть работы достаётся процессору, не сказано. Поэтому и не утверждаем: то, что процессор при кодировании работает, из документации следует, а то, что на нём выполняется именно Tier-2, — нет. Этот пункт стоит в разделе 15, среди непроверенного.
Вся работа на процессоре входит в измеряемое время у обеих сторон и в обоих режимах. Вынести эту часть за скобки было бы некорректно: у fvJPEG2000 она входит, значит и у nvJPEG2000 должна входить. Диск исключён везде: результат никуда не пишется, иначе мы измеряли бы и скорость накопителя, а ожидание очередей чтения и записи в отсчёт не входит. Число кадров в секунду в многопоточном режиме считается как общее число кадров, делённое на время самого медленного потока.
4.2.1. Границы отсчёта в примерах NVIDIA устроены иначе
В открытом наборе примеров NVIDIA CUDALibrarySamples границы отсчёта устроены иначе, и это стоит рассмотреть подробнее: без этого результаты из их публикаций и результаты из этой статьи выглядят сравнимыми, а они не сравнимы.
Пример декодирования. Кадры обрабатываются строго по одному: одно состояние декодера, одна очередь заданий на видеокарте, после каждого кадра — ожидание. Ключ -b, который в описании называется размером пачки, группирует только чтение файлов с диска и на способ обработки не влияет.
В примере измеряется время работы одной функции, nvjpeg2kDecodeImage. Вызов асинхронный: он ставит работу в очередь заданий видеокарты и сразу возвращает управление, поэтому часами процессора его не измерить. В примере это учтено. Время снимается парой событий CUDA на той же очереди, до вызова и после, и это правильный способ. К измерению самого декодирования вопросов нет.
Вопрос ко второму слагаемому. Разбор сжатого изображения выполняет nvjpeg2kStreamParse. Это Tier-2, по документации самой NVIDIA первая стадия декодирования. Его время измеряется отдельно, часами процессора, и прибавляется к сумме. Вот как это сделано в nvjpeg2000DecodeSample.cpp (аргументы вызова опущены, остальное дословно):
auto io_start = perfclock::now();
nvjpeg2kStreamParse(…);
auto io_end = perfclock::now();
double parse_time = std::chrono::duration_cast<std::chrono::seconds>(io_end - io_start).count();
…
time += static_cast<double>(loopTime / 1000.0);
time += parse_time;
Длительность переводится в целые секунды, а не в дробные. Всё, что короче секунды, обращается в ноль, а разбор кадра идёт миллисекунды. Значит time += parse_time прибавляет всегда ровно ноль. Выделение буферов на видеокарте, чтение файла и запись результата в измеряемое время не входят, а готовый кадр в оперативную память в измеряемом цикле не выгружается вовсе.
Это не выбор границы отсчёта, а ошибка в измерении. Решить, что считать частью алгоритма, можно по-разному, и об этом законно спорить. Здесь же слагаемое в коде написано, но значения у него нет ни при каком кадре и ни на каком оборудовании. Сколько на этом теряется, видно по таблице стадий в разделе 8: Tier-2 при декодировании занимает от 15 % времени кадра на 2K до 29 % на 4K. Это доли fvJPEG2000, потому что библиотека nvJPEG2000 время по стадиям не показывает и её долей мы не знаем. Но стадия та же и выполняется так же на процессоре, так что величина того же порядка.
Поэтому числа nvJPEG2000 в этой статье получены не этим примером. Для nvJPEG2000 написана своя программа (bench/nvj2k_bench-02/nvj2k_bench-02.cpp, раздел 14). Счётчик в ней запускается перед nvjpeg2kStreamParse и останавливается после декодирования, когда видеокарта закончила работу, то есть Tier-2 входит в измеряемое время ровно так же, как он входит у fvJPEG2000. Обе стороны измерены по одному правилу, иначе сравнивать нечего.
Отсюда следствие для читателя. Число, напечатанное примером NVIDIA, и число из этой статьи ставить рядом нельзя: первое показывает время работы части алгоритма, второе относится ко всему декодированию. Первое всегда будет выглядеть лучше.
Пример кодирования. Кадры идут по одному так же. Здесь измеряется время всего цикла обработки кадра, и выгрузка сжатого изображения в оперативную память (nvjpeg2kEncodeRetrieveBitstream) в него входит. Загрузка исходного кадра на видеокарту остаётся снаружи, она делается при чтении файла. Это ровно та граница, что и у нас в режиме одиночных кадров, и к ней вопросов нет.
4.3. Оптимум ищется перебором, а не назначается
Число потоков на центральном процессоре и размер пачки — не «разумный выбор», а найденные перебором значения. Оптимум лежит внутри диапазона, и в разных задачах он может отличаться. У кодера fvJPEG2000 на 2K пачка заметно помогает: восемь потоков с пачкой два дают 1914 кадров в секунду против 1776 у восьми потоков без пачки, а шестнадцать потоков оказываются медленнее восьми. На 4K картина другая: 8×1, 16×2 и 8×4 дают 616, 610 и 605 кадров в секунду — это одно и то же значение в пределах разброса между сериями измерений. Крупный кадр загружает видеокарту и без пачки, добавлять к нему нечего.
Поэтому в условиях измерений публикуется весь список использованных сочетаний, а не победившая комбинация: воспроизводится процедура, а не итоговый результат. Перебирались шесть комбинаций, в записи «число потоков × размер пачки»: 8×1, 8×2, 16×2, 8×4, 32×1 и 32×2.
В сетке есть точки, где потоков много, а кадр в потоке один: 32×1 и 32×2. У процессора 16 ядер и 32 логических ядра, так что 32 потока — это вся машина, и такая точка отвечает на прямой вопрос: не окажется ли, что библиотеке достаточно дать больше потоков. Ответ в разделе 6, и он не одинаков для двух кодеков.
В таблицах ниже приводятся все шесть комбинаций и отдельно лучшая для каждого кодека. Дальше в тексте вместо «лучшее сочетание количества потоков и размера пачки» для краткости говорится лучшее сочетание потоков и пачки.
Важная оговорка: пачка у двух кодеков устроена по-разному, иначе одно и то же слово в таблицах будет означать две разные вещи. Сначала о самой записи: 8×2 — это 8 потоков процессора, и в каждом из них 2 кадра одновременно находятся в работе на видеокарте. Потоков ровно 8 при любом размере пачки, они не удваиваются; удваивается число заданий, которые видеокарта считает в один и тот же момент — не 8, а 16.
У fvJPEG2000 эти 2 кадра уходят в кодек одним вызовом: пачка настоящая, кодек обрабатывает их как одно задание. Это штатная возможность Fastvideo SDK.
У nvJPEG2000 такого вызова нет. Ни одна функция библиотеки не принимает массив изображений — только одно изображение за вызов. Поэтому загрузка видеокарты набирается иначе: в каждом потоке заводится столько независимых состояний кодека и столько очередей заданий видеокарты (CUDA stream), сколько указано в размере пачки. Поток отправляет кодирование первого кадра в свою первую очередь и, не дожидаясь результата, сразу отправляет второй кадр во вторую, и только потом ждёт оба. Вызовы асинхронные, очереди независимые, поэтому оба кадра считаются на видеокарте одновременно.
Сделано это штатными средствами самой библиотеки NVIDIA и CUDA: и несколько состояний кодека, и очереди заданий, и асинхронность вызовов — её обычные возможности, обходных приёмов здесь нет. Нет в библиотеке только вызова, который принимает несколько кадров сразу, поэтому порядок вызовов приходится выстраивать самому.
Сказать это стоит ещё и потому, что само собой это не работает. Программа, которая просто вызывает nvJPEG2000 по одному кадру в потоке (а именно так устроены примеры NVIDIA), получит 8 одновременных заданий вместо 16, и результат окажется слабее. Насколько слабее, видно при одном и том же числе потоков: на кадре 2K с потерями, 8 потоков дают кодеру 205 кадров в секунду без приёма и 245 с пачкой два, то есть в 1,2 раза больше. У декодера на этой же задаче исходная точка измерена ненадёжно (раздел 8), поэтому возьмём соседнюю: на 4K с потерями 8 потоков дают 208 кадров в секунду без приёма и 428 с четырьмя кадрами в потоке — вдвое больше.
Мы всё равно приводим именно эти значения и берём их за лучшие для nvJPEG2000: сравнивать надо с тем максимумом, который из библиотеки можно получить, а не с тем, что даёт стандартный вариант её использования.
4.4. Что не измерялось
Тайлы, декодирование выбранной области, разрядность выше восьми бит, многокомпонентные преобразования сверх стандартных, работа на Jetson. Часть этого есть только у одной из сторон и сравнивается по таблице возможностей, а не по скорости; часть — отдельная постановка задачи.
5. Стенд: NVIDIA GeForce RTX 4090, Fastvideo SDK и nvJPEG2000
Результат производительности без описания условий, в которых он получен, бесполезен. Здесь перечислены все условия: и параметры программ, и оборудование.
| Что | Значение |
|---|---|
| Видеокарта | NVIDIA GeForce RTX 4090, 24 ГБ |
| Драйвер видеокарты | 610.88 |
| Максимальная мощность видеокарты | 450 Вт |
| Процессор | AMD Ryzen 9 7950X, 16 ядер, 32 логических ядра |
| Оперативная память | 128 ГБ |
| Кодек JPEG2000 Fastvideo (FV) | Fastvideo SDK 0.23.1.0, CUDA 13.3 |
| Библиотека nvJPEG2000 (NV) | версия 0.11.0.51 |
| Операционная система | Windows 11 |
| Чем измерялся fvJPEG2000 | приложение из Fastvideo SDK |
| Чем измерялся nvJPEG2000 | программа измерений Fastvideo nvj2k_bench-02, многопоточный запуск с несколькими кадрами в работе; штатный пример NVIDIA не использовался |
| Скорость шины, по измерению | 25,2 ГБ/с с процессора на видеокарту |
| Серий измерений на каждую точку | 3, в таблицах медиана |
| Дата измерений | 31 августа 2026 |
Отдельно об одном условии, потому что от него зависит чтение всех таблиц. Библиотека nvJPEG2000 измерялась не тем примером, который лежит в наборе NVIDIA. Тот пример обрабатывает кадры по одному и считает время по своим границам отсчёта (раздел 4.2, раздел 4.2.1). Для сравнения написана своя программа: она запускает ту же библиотеку в нескольких потоках, с несколькими её состояниями и очередями заданий в каждом потоке, и меряет время по тем же правилам, что и у fvJPEG2000. Из библиотеки таким образом выжимается заметно больше (раздел 4.3 и таблица в начале статьи), и в таблицах разделов 6 и 7 стоит именно этот, лучший результат nvJPEG2000.
Все измерения выполняет один скрипт: он готовит эталонные файлы, подбирает качество, измеряет производительность обеих реализаций, проверяет качество восстановленного изображения и печатает готовую таблицу. Полный прогон с тремя повторами на точку занимает около часа. Сколько кадров обрабатывать в каждом тесте, скрипт выбирает сам: сначала короткая разведка скорости, потом расчёт числа кадров так, чтобы измерение на каждой точке длилось одинаково. Поэтому быстрая и медленная точки меряются одинаково долго, а не одинаковым числом кадров, и на любой видеокарте прогон устроен так же.
6. Скорость кодирования JPEG2000 на RTX 4090
Дальше идут результаты. Их ценность целиком держится на разделах 3 и 4: одинаковый размер файла у обеих сторон, одинаковые параметры сжатия и выбранный режим работы.
Во всех клетках таблиц ниже стоит число кадров в секунду. Строка «одиночный» получена в режиме одиночных кадров, остальные шесть — в многопоточном режиме при разных сочетаниях «число потоков × размер пачки». Столбцы NV — это библиотека nvJPEG2000, запущенная по схеме Fastvideo, а не штатный пример NVIDIA (раздел 5). Лучшее значение в столбце выделено жирным, и это же сочетание названо в строке «оптимум». Кадры разнесены по двум таблицам.
Нижняя строка — сколько логических ядер центрального процессора в среднем занято, когда кодек работает в своём оптимуме. Логическое ядро — это то, что операционная система показывает как отдельный процессор; у этой машины 16 ядер и 32 логических, так что 32 — вся машина. Величина измерена отдельно, вместе с энергией (раздел 11): на сервере, где рядом работает что-то ещё, занятые ядра — такой же ресурс, как ватты.
Кадр 2K, 1920 × 1080
| Режим | С потерями, FV | С потерями, NV | Без потерь, FV | Без потерь, NV |
|---|---|---|---|---|
| 8×1 | 1776 | 205 | 1120 | 158 |
| 8×2 | 1914 | 245 | 1179 | 164 |
| 16×2 | 1687 | 278 | 1039 | 178 |
| 8×4 | 1910 | 226 | 1136 | 165 |
| 32×1 | 1308 | 275 | 858 | 164 |
| 32×2 | 1450 | 292 | 912 | 187 |
| одиночный | 381 | 198 | 329 | 146 |
| Оптимум | 8×2 | 32×2 | 8×2 | 32×2 |
| Логических ядер | 7,0 | 29,5 | 7,2 | 29,8 |
Кадр 4K, 3840 × 2160
| Режим | С потерями, FV | С потерями, NV | Без потерь, FV | Без потерь, NV |
|---|---|---|---|---|
| 8×1 | 616 | 134 | 371 | 62 |
| 8×2 | 572 | 148 | 369 | 63 |
| 16×2 | 610 | 160 | 322 | 64 |
| 8×4 | 605 | 143 | 333 | 63 |
| 32×1 | 565 | 158 | 294 | 63 |
| 32×2 | — | — | — | — |
| одиночный | 195 | 128 | 140 | 56 |
| Оптимум | 8×1 | 16×2 | 8×1 | 16×2 |
| Логических ядер | 7,5 | 14,7 | 7,6 | 14,8 |
Прочерк в строке 32×2 у кадра 4K означает, что это сочетание не измерялось вовсе: тридцать два потока по два кадра на 4K не помещаются в память видеокарты у кодера fvJPEG2000. Сочетание, выпавшее у одного кодека, мы не меряем и у второго, иначе в таблице стояла бы клетка, заполненная с одной стороны и пустая с другой, и читалась бы она как «второй кодек здесь медленнее», хотя он просто не измерялся.
Во сколько раз fvJPEG2000 быстрее nvJPEG2000 при кодировании?
На 2K с потерями — 1914 кадров в секунду у fvJPEG2000 против 292 у nvJPEG2000, это 6,6 раза; на 4K с потерями — 616 против 160, то есть 3,9 раза. Сжатые файлы у обоих кодеков одного размера.
| Кодирование | FV быстрее NV потоки и пачка |
FV быстрее NV одиночные кадры |
|---|---|---|
| 2K, с потерями | 6,55× | 1,93× |
| 2K, без потерь | 6,31× | 2,25× |
| 4K, с потерями | 3,86× | 1,53× |
| 4K, без потерь | 5,77× | 2,49× |
Кодер fvJPEG2000 в режиме одиночных кадров быстрее кодера nvJPEG2000 в 1,5–2,5 раза. Это латентность кодирования — время отклика на один кадр, а не пропускная способность. В миллисекундах на кадр, fvJPEG2000 против nvJPEG2000: 2,6 против 5,1 и 3,0 против 6,8 на 2K, 5,1 против 7,8 и 7,1 против 17,8 на 4K. Речь именно о кодировании; с декодированием картина другая, она в следующем разделе.
Кодер nvJPEG2000 почти не ускоряется от многопоточного режима. Все его результаты лежат в узкой полосе: от 205 до 292 кадров в секунду на 2K и от 134 до 160 на 4K. Переход от одиночных кадров к 8 потокам увеличивает скорость всего на 3,5 %; дальше немного прибавляет только более плотная загрузка карты. Для сравнения, fvJPEG2000 на той же задаче от одиночных кадров к 8 потокам ускоряется в 4,7 раза.
Отдельно про 32 потока — ту самую новую часть сетки. Проверялось, не окажется ли, что библиотеке достаточно просто дать больше потоков процессора. У nvJPEG2000 на 2K с потерями 32×2 действительно вышло вперёд, 292 кадра в секунду против 278 у 16×2, но это прибавка в 5 %, а повторы на этих точках расходятся сильнее, так что считать её победой нельзя; на 4K тридцать два потока не дают ничего. У fvJPEG2000 тридцать два потока на кодировании работают хуже восьми: 1308 и 1450 против 1914. Кодер и так занимает всю карту при 8 потоках, а лишние потоки только добавляют работы процессору.
Обратите внимание на строку с логическими ядрами. В своём оптимуме fvJPEG2000 занимает 7,0–7,6 логического ядра, nvJPEG2000 — от 14,7 до 29,8. То есть кодер NVIDIA не только выдаёт меньше кадров, но и берёт на это вдвое больше логических ядер процессора на 4K и вчетверо на 2K.
Проверено, не ошибка ли это в программе для тестирования. На кадре 2K, в лучшем для nvJPEG2000 сочетании потоков и пачки, одним запуском без усреднения кодер даёт 279 кадров в секунду; если убрать из того же цикла копирование изображения в память видеокарты — 323, на 16 % больше. То есть и без копирования кодер остаётся медленнее fvJPEG2000 в 6 раз и от потоков почти не ускоряется. А декодер той же библиотеки в этой же программе, на этой же карте и по той же схеме потоков ускоряется в 3,5 раза. Значит дело не в программе для тестирования, а в том, что кодер и декодер nvJPEG2000 устроены по-разному.
7. Скорость декодирования JPEG2000 на RTX 4090
Таблицы устроены так же, как в предыдущем разделе: строки — режимы и сочетания, столбцы — кодеки, нижняя строка — сколько логических ядер процессора занято в оптимуме.
Кадр 2K, 1920 × 1080
| Режим | С потерями, FV | С потерями, NV | Без потерь, FV | Без потерь, NV |
|---|---|---|---|---|
| 8×1 | 425 | 310 | 272 | 360 |
| 8×2 | 640 | 751 | 365 | 403 |
| 16×2 | 873 | 764 | 425 | 412 |
| 8×4 | 1024 | 1033 | 425 | 438 |
| 32×1 | 596 | 532 | 395 | 369 |
| 32×2 | 883 | 719 | 436 | 411 |
| одиночный | 144 | 298 | 116 | 237 |
| Оптимум | 8×4 | 8×4 | 32×2 | 8×4 |
| Логических ядер | 7,6 | 4,1 | 28,6 | 3,5 |
Кадр 4K, 3840 × 2160
| Режим | С потерями, FV | С потерями, NV | Без потерь, FV | Без потерь, NV |
|---|---|---|---|---|
| 8×1 | 244 | 208 | 133 | 108 |
| 8×2 | 348 | 318 | 130 | 125 |
| 16×2 | 377 | 323 | 140 | 125 |
| 8×4 | 350 | 428 | 120 | 134 |
| 32×1 | 330 | 207 | 145 | 108 |
| 32×2 | 394 | 335 | 141 | 124 |
| одиночный | 96 | 193 | 59 | 91 |
| Оптимум | 32×2 | 8×4 | 32×1 | 8×4 |
| Логических ядер | 25,9 | 4,8 | 27,8 | 3,4 |
Одна клетка в таблице 2K требует оговорки: у nvJPEG2000 на 8×1 измерения расходятся надвое: та же точка на той же машине даёт то 309 кадров в секунду, то 539. В таблице стоит медиана прогона, 310; разбор в разделе 8.
Здесь картина другая, и она зависит от режима.
Какой декодер JPEG2000 на видеокарте быстрее?
В лучшем сочетании потоков и пачки кодеки идут вровень: на 2K с потерями 1024 кадра в секунду у fvJPEG2000 против 1033 у nvJPEG2000, на 2K без потерь 436 против 438: разница меньше процента, и оба раза в пользу nvJPEG2000. На 4K расхождение больше и идёт в обе стороны: и там и там 8 %, только с потерями впереди nvJPEG2000 (428 против 394), а без потерь впереди fvJPEG2000 (145 против 134). В режиме одиночных кадров nvJPEG2000 быстрее в 1,5–2,1 раза.
| Декодирование | Потоки и пачка | Одиночные кадры |
|---|---|---|
| 2K, с потерями | NV на 0,8 % | NV в 2,07 раза |
| 2K, без потерь | NV на 0,3 % | NV в 2,04 раза |
| 4K, с потерями | NV на 8 % | NV в 2,01 раза |
| 4K, без потерь | FV на 8 % | NV в 1,53 раза |
Столбец максимальной скорости повторяет сказанное выше: расхождение не больше 8 % и в обе стороны. В режиме одиночных кадров впереди nvJPEG2000: на трёх сочетаниях условий из четырёх он быстрее ровно вдвое. Там, где важно время декодирования одного кадра, это существенно.
Оптимум у декодера fvJPEG2000 сместился к 32 потокам на трёх задачах из четырёх, и прибавка там небольшая: на 2K без потерь 32×2 даёт 436,2 кадра в секунду против 424,6 у 16×2, то есть 2,7 %. Строку логических ядер стоит читать вместе с этим. Там, где оптимум оказался на 32 потоках, декодер занимает от 26 до 29 логических ядер; там, где на восьми, — 7,6. Чего стоит эта прибавка, посчитано в разделе 11: на 16×2 та же задача занимает вдвое меньше ядер. Но порядок виден, и выбор между несколькими процентами скорости и заметно меньшим числом занятых логических ядер в реальной системе.
Один из этих оптимумов условный. На 4K без потерь 32×1 даёт 145 кадров в секунду против 141 у 32×2 и 140 у 16×2 — это одно и то же значение в пределах разброса между повторами, и лучшую точку тут можно назвать только условно. Для кодера такая же оговорка сделана в разделе 4.3; к декодеру она относится ровно так же.
Файлы у двух кодеров имеют одинаковый размер, но внутри устроены по-разному, и теоретически один из них мог давать декодеру меньше работы. Проверяется это перекрёстным декодированием: каждый декодер запускается на файле, сделанном чужим кодером. Разница ни в одном из восьми сочетаний условий (два декодера, два кадра, два режима сжатия) не превысила 1,4 %, а в шести случаях из восьми она меньше половины процента. Значит сравнение декодеров корректно: файлы дают одинаковую нагрузку, и результат относится именно к декодерам.
8. Из чего складывается ускорение: стадии JPEG2000

Откуда берётся разница в скорости между кодеками?
Она складывается из трёх слагаемых: как распараллелена каждая стадия внутри, есть ли пачка и как работает многопоточность.
Внутри каждой стадии. В таблицах этого уровня не видно вовсе: каждый этап (вейвлет-преобразование, квантование, EBCOT Tier-1) сам раскладывается на тысячи параллельных потоков видеокарты (в терминах CUDA — threads). От того, насколько эффективно это сделано, зависит время кадра в любом режиме.
Пачка склеивает несколько кадров в один: видеокарта увидит один большой кадр вместо нескольких мелких. У nvJPEG2000 пачки нет, и её роль играет приём из раздела 4.3 — несколько кадров, одновременно находящихся в работе внутри одного потока. Перекрытия этапов ни то, ни другое не даёт, только увеличивает загрузку. Отсюда следствие, которое подтвердилось на измерениях: пачка помогает на 2K и бесполезна на 4K, где кадр и так загружает карту. У кодера fvJPEG2000 на 4K лучшим вариантом оказался 8×1, восемь потоков вообще без пачки, но обгоняет он ближайшего соседа меньше, чем расходятся сами повторы, так что правильнее сказать так: на 4K пачка не даёт ничего.
Многопоточность позволяет обрабатывать разные кадры одновременно. У fvJPEG2000 есть и отдельные пулы чтения и записи, но в этих тестах они не участвуют: диск исключён.
Насколько велик вклад каждого приёма, видно из отдельного разбора. Возьмём кадр 2K, сжатый с потерями. Новых измерений здесь нет: все скорости взяты из таблиц разделов 6 и 7, из строк «2K, с потерями». За единицу берётся режим одиночных кадров; отношение к нему у сочетания 8×1 показывает вклад многопоточности, отношение оптимума к 8×1 — вклад пачки и более плотной загрузки, последняя строка — произведение первых двух. Отношения округлены, а считаются по неокруглённым кадрам в секунду.
| Кодер | fvJPEG2000 | nvJPEG2000 |
|---|---|---|
| Одиночные кадры | 381 | 198 |
| 8×1 | 1776 | 205 |
| Оптимум | 1914 (8×2) | 292 (32×2) |
| Что дала многопоточность | 4,7× | 1,0× |
| Что дал переход к оптимуму | 1,1× | 1,4× |
| Итого быстрее | 5,0× | 1,5× |
Строка «что дал переход к оптимуму» у двух кодеров означает разное. У fvJPEG2000 лучшим оказалось 8×2: потоков столько же, добавилась только пачка, поэтому 1,1× здесь и есть вклад пачки в чистом виде. У nvJPEG2000 лучшим оказалось 32×2: и потоков вчетверо больше, и по два кадра одновременно в работе в каждом, поэтому 1,4× это вклад обоих изменений сразу. Пачки у nvJPEG2000 нет вовсе: два кадра одновременно набираются приёмом из раздела 4.3 — отдельными состояниями кодека и отдельными очередями заданий внутри потока.
Кодер nvJPEG2000 от многопоточности не ускоряется вовсе: множитель 1,035, то есть три с половиной процента, что меньше разброса самих измерений. Всё, что он получает, даёт не многопоточность, а более плотная загрузка карты. В сумме выходит 1,5 раза против 5,0 у fvJPEG2000, и именно отсюда берётся разрыв, который в разделе 6 достигает шести с половиной раз.
У декодеров картина другая, и разрыв там гораздо меньше. Скорости — из таблицы раздела 7, строка «2K, с потерями». У обоих декодеров лучшее сочетание — восемь потоков, столько же, сколько в строке 8×1: меняется только число кадров, одновременно находящихся в работе внутри потока. Значит здесь обе стороны сравниваются в чистом виде.
| Декодер | fvJPEG2000 | nvJPEG2000 |
|---|---|---|
| Одиночные кадры | 144 | 298 |
| 8×1 | 425 | 310 |
| Оптимум | 1024 (8×4) | 1033 (8×4) |
| Что дала многопоточность | 3,0× | 1,0× |
| Что дал переход к оптимуму | 2,4× | 3,3× |
| Итого быстрее | 7,1× | 3,5× |
Декодер fvJPEG2000 ускоряется от потоков втрое, а пачка из четырёх кадров добавляет ещё 2,4 раза, вместе — 7,1. У декодера nvJPEG2000 ступени распределены совсем иначе: многопоточность не даёт ему почти ничего, зато четыре кадра, одновременно находящиеся в работе внутри потока, ускоряют его в 3,3 раза; вместе выходит 3,5 раза.
Но результаты декодера nvJPEG2000 надёжны не полностью. Множитель 1,0 посчитан от сочетания 8×1, а это единственное место в статье, где измерения расходятся надвое: то 309 кадров в секунду, то 539. Пока эта точка не выяснена, делить ускорение декодера на две ступени нельзя: оба множителя считаются от неё. Надёжно только общее ускорение, 3,5 раза от режима одиночных кадров до лучшего сочетания, и оно от спорной точки не зависит. Декодер nvJPEG2000 начинает с вдвое лучшего одиночного кадра, но разгоняется вдвое хуже, поэтому в оптимуме кодеки и сходятся.
Как распределено время кадра между отдельными стадиями JPEG2000. Тестовое приложение Fastvideo с ключом -info выводит время каждой стадии по отдельности. Стадии в таблице ниже названы так же, как в этом выводе, и идут в том же порядке: у кодера от исходных пикселов к сжатому изображению, у декодера в обратную сторону. Числа — медиана пяти запусков той же серии измерений, кадр 2K и кадр 4K, сжатие с потерями; логи всех запусков лежат в репозитории. Разбивка есть только у fvJPEG2000: библиотека nvJPEG2000 время по стадиям не показывает. Поэтому таблица ниже описывает устройство одного кодека, а не преимущество одного над другим; сравнение двух кодеков по стадиям тоже есть, но оно сделано иначе, снаружи профилировщиком, и приведено в конце этого раздела.
Это оценка, а не измерение, и вот почему. Времена стадий кодек выдаёт только для одиночного кадра, и в каждую стадию попали разовые расходы: первый запуск вычислительных ядер на видеокарте, выход карты на режим и синхронизации, которые вставляет сам ключ. Насколько это много, видно в двух нижних строках таблицы: сумма стадий 4,65 мс против реальных 2,62 мс на кодировании 2K. Лишние две миллисекунды размазаны по стадиям, и сильнее всего они искажают быстрые стадии на мелком кадре: преобразование цвета со сдвигом уровня занимает 0,69 мс на 2K и 0,74 мс на 4K, хотя кадр вчетверо больше. Доли в таблице надо читать как порядок величины, а не как точные проценты.
Две строки стоит расшифровать. Преобразование цвета и сдвиг уровня у кодера идут в начале, у декодера — в обратную сторону и в конце, поэтому в таблице это одна строка с числами по обе стороны. Сборка буферов — сведение готовых кодовых блоков в один непрерывный буфер перед передачей на процессор. Своей строки нет у квантования: отдельной стадией оно не выделено, а выполняется на видеокарте внутри ядра контекстного моделирования, то есть внутри EBCOT. Доли округлены до целых, поэтому сумма по столбцу может дать 99 или 101 %; нижняя строка — время одного кадра в режиме одиночных кадров из разделов 6 и 7, для сравнения с суммой стадий.
Доли стадий у fvJPEG2000, режим одиночных кадров, сжатие с потерями:
| Стадия | Где | Кодирование 2K | Кодирование 4K | Декодирование 2K | Декодирование 4K |
|---|---|---|---|---|---|
| Преобразование цвета и сдвиг уровня | видеокарта | 15 % | 11 % | 5 % | 4 % |
| Вейвлет-преобразование | видеокарта | 8 % | 8 % | 6 % | 6 % |
| EBCOT Tier-1 | видеокарта | 57 % | 51 % | 73 % | 60 % |
| Сборка буферов | видеокарта | 4 % | 3 % | — | — |
| Копирование через шину | — | 1 % | 2 % | 1 % | 1 % |
| Tier-2 | процессор | 15 % | 26 % | 15 % | 29 % |
| Сумма по стадиям, мс | — | 4,65 | 6,67 | 8,05 | 12,28 |
| Реальное время кадра, мс | — | 2,62 | 5,13 | 6,96 | 10,41 |
Три вывода из этой прикидки достаточно крупные, чтобы разовые расходы их не отменили.
Главная работа — энтропийное кодирование EBCOT Tier-1. От половины до трёх четвертей всего времени, и именно оно определяет скорость кодека. Всё остальное вместе весит меньше.
Работа на процессоре растёт с размером кадра, работа на видеокарте — почти нет. Tier-2 при кодировании занимает 0,72 мс на 2K и 1,73 мс на 4K, при декодировании 1,23 и 3,60 мс, то есть в 2,5–3 раза больше на вчетверо большем кадре. Стадии на видеокарте за это время прибавляют десятые доли миллисекунды. Это та самая работа на процессоре, о которой сказано в разделе 4.2, и на крупном кадре она из мелочи превращается в четверть времени.
Обратное преобразование цвета и сдвиг уровня у декодера весят мало — 0,39 мс из восьми на 2K. Это важно для сравнения декодеров: тестовая программа nvJPEG2000 оставляет результат раздельными плоскостями и этой работы не делает (раздел 15). Вклад её мал, и на вывод она не влияет.
Насколько результаты повторяются. Каждая точка измерялась трижды, в таблицы идёт медиана. Точка, у которой три повтора разошлись больше чем на 7 %, измеряется заново, до двух дополнительных запусков, и медиана берётся по всем пяти. В среднем разброс невелик: у fvJPEG2000 4,5 % на кодировании и 2,1 % на декодировании, у nvJPEG2000 2,8 % и 3,6 %.
Но средним тут ограничиваться нельзя: из ста десяти точек с повторами одиннадцать разошлись сильнее семи процентов и после добавочных запусков. В отчёте прогона они названы поимённо, с разбросом и числом запусков. У большинства из них причина видна по мощности видеокарты: у медленного повтора карта берёт заметно меньше ватт, то есть для неё не было нагрузки: процессорное время в этот момент забрал кто-то другой. Троттлинг выглядел бы наоборот, с мощностью у предела. Главные выводы этой статьи опираются на разницу в разы, то есть заведомо больше любого такого разброса, но отдельно взятую ячейку таблицы стоит читать с этой оговоркой.
Один частный случай устроен сложнее остальных, и о нём стоит рассказать подробно — не потому, что он важен для выводов, а потому, что именно ради таких вещей и пишутся разделы про методику. Декодирование nvJPEG2000, 2K с потерями, 8×1: здесь повторы расходятся не разбросом, примерно в 2 раза. Мы перемеряли эту точку двадцать раз подряд. Девять запусков дали 309 кадров в секунду, одиннадцать — 539, и внутри каждой группы значения совпадают до десятых долей. Состояние выбирается один раз при старте программы и держится весь запуск, от первого кадра до последнего.
Это не нагрев и не посторонняя нагрузка. Частота видеокарты в обоих состояниях одна и та же, 2745 МГц, температура 46–52 градуса, загрузка карты 97 и 98 процентов; соседняя точка 8×2, которая измерялась через одну, всё это время шла ровно. Разница в другом: в медленном состоянии на кадр уходит на 45 % больше процессорного времени: 13,3 миллисекунды на кадр против 9,2. Карта, получая меньше работы, берёт 135 ватт вместо 171. Значит тормозит процессорная часть декодирования.
Два объяснения мы проверили и отбросили. Размещение потоков по ядрам: процессор стенда состоит из двух кристаллов по восемь ядер, и обмен между кристаллами дороже, чем внутри одного, и казалось правдоподобным, что быстрое состояние — это когда все восемь потоков попали на один кристалл. Но в 36 запусках, каждый с явно заданным набором ядер, оба состояния воспроизводятся в любом наборе, в том числе когда все восемь потоков сидят на восьми разных ядрах одного кристалла. Способ ожидания видеокарты: CUDA умеет ждать четырьмя способами: активным опросом, уступая квант времени, блокирующим ожиданием и выбором по умолчанию, и от этого заметно зависит, сколько процессорного времени уходит на само ожидание. Мы добавили в тестовую программу ключ, задающий способ явно, и сняли по шесть запусков на каждый: оба состояния появляются во всех четырёх. Промежуточных значений нет ни одного: 306–311 или 531–541 кадра в секунду, и ничего между ними.
Зато нашлось, от чего раздвоение зависит: от числа потоков. Тот же прогон, шесть запусков на вариант. При одном, двух и четырёх потоках все значения ложатся рядом: 274, 309 и 309 кадров в секунду. При восьми и шестнадцати появляются оба состояния. При тридцати двух все шесть запусков дали быстрое, 531–534. Верхнее состояние восьми потоков, 530–540, совпадает и с тем, что тридцать два потока дают всегда, и с числом 532 из соседней ячейки таблицы. Значит быстрое состояние — это норма, а медленное — срыв. Совпадение медленного состояния с уровнем двух потоков объяснением не является: в нём на кадр уходит 13,3 миллисекунды процессорного времени, то есть заняты около четырёх логических ядер, а двум потокам столько взять неоткуда. Дело не в том, что часть потоков простаивает, а в том, что каждый кадр обходится дороже. Что именно дорожает, должен показать профилировщик на быстром и медленном запуске рядом; это следующий шаг.
В таблице раздела 7 стоит медиана прогона, 310. Соседи по таблице при этом говорят в пользу 539: у nvJPEG2000 на декодировании тридцать два потока по одному кадру дают ровно столько же, сколько восемь потоков по одному кадру: 369 против 360 на 2K без потерь, 207 против 208 на 4K с потерями, 108 против 108 на 4K без потерь. На 2K с потерями 32×1 даёт 532, и со значением 539 эта ячейка встаёт в тот же ряд, а со значением 310 остаётся единственным исключением. Мы всё же оставляем измеренное и называем сомнение вслух: подгонять число под правило — верный способ получить красивую таблицу и неверный результат. Логи всех двадцати запусков лежат в репозитории.
Отдельно про зависимость от объёма данных. Здесь важно не смешивать два режима измерений. При лучшем сочетании потоков и пачки сжатие без потерь даёт впятеро больше данных, обе стороны упираются в скорость энтропийного декодирования, и результаты почти сравниваются: на 2K получается 436 кадров в секунду у fvJPEG2000 против 438 у nvJPEG2000. В режиме одиночных кадров картина другая: там решают постоянные расходы на кадр, и в них fvJPEG2000 проигрывает. На 2K разрыв между кодеками растёт лишь с 3,6 миллисекунды на кадр при сжатии с потерями до 4,4 без потерь, хотя работы становится впятеро больше. То есть разрыв почти не зависит от объёма данных, а это и есть признак постоянных расходов, а не самой работы.
Что показывает профилировщик
У таблицы выше два ограничения: результаты измерений есть только для режима обработки одиночных кадров и только для fvJPEG2000. Эти результаты можно получить и извне — мы получили их профилировщиком NVIDIA Nsight Systems. Он строит шкалу времени для всего теста: на ней видно каждый запуск ядра на видеокарте и каждое копирование через шину, кадр за кадром, и видно, что с чем совпадает по времени. Ни один из кодеков для этого не менялся, командные строки взяты из логов той же серии измерений, по которой сделаны таблицы разделов 6 и 7.
Ядро. Ядро (kernel) — это функция, которую видеокарта выполняет по команде процессора; в CUDA процессор называют хостом, видеокарту — устройством, и память у них раздельная. От обычной функции ядро отличается тем, что при вызове выполняется не один раз, а сразу тысячами параллельных нитей, каждая над своим куском данных. Дальше слово «ядро» означает только это.
Одно слово, три разных смысла — их стоит различать. Ядро (kernel) — функция, которую выполняет видеокарта; дальше речь только о ней. Ядрами CUDA в спецификациях видеокарт называют вычислительные блоки внутри неё, на RTX 4090 их шестнадцать тысяч; работу по ним карта распределяет сама. Ядра процессора — это счётные ядра центрального процессора, на котором работает программа: на стенде их шестнадцать, а логических, какими их показывает операционная система, тридцать два. О них идёт речь в разделах 6, 7 и 11.
Работа кодека — это цепочка запусков ядер. На видеокарте выполняются стадии, идущие друг за другом: цветовое преобразование со сдвигом уровня, вейвлет-преобразование, кодирование кодовых блоков (EBCOT Tier-1) и сборка буферов кодовых блоков. Почти у каждой стадии своё ядро, а у крупной — несколько: Tier-1 при кодировании у нас делают два ядра, а вейвлет-преобразование — несколько десятков запусков на кадр, по уровням разложения и каналам. Квантование своего ядра не имеет вовсе: оно выполняется внутри ядра контекстного моделирования, то есть входит в EBCOT, и отдельной строки в записи профилировщика у него нет. Кодек копирует кадр в память видеокарты, запускает ядра первой стадии, затем второй и так до конца; результат каждой стадии остаётся в памяти карты и служит входом следующей, а в оперативную память возвращается только сжатый файл. Упаковка кодовых блоков в файл — Tier-2 — на видеокарте не считается вовсе: это работа процессора, и в таблице выше она стоит отдельной строкой.
Что именно измеряется. Дальше — время, в течение которого ядра занимали видеокарту, а не время по часам компьютера, прошедшее от подачи кадра до готового файла. Оно не зависит от того, сколько кадров идёт одновременно, поэтому его можно сравнивать между кодеками напрямую. Условия прежние: при кодировании обоим кодекам даётся один и тот же исходный кадр и сжатые файлы имеют одинаковый размер; при декодировании каждый кодек разбирает файл своего кодера, тоже одинакового размера. Результаты измерений времени работы получены для режима обработки одиночных кадров.
Время стадии EBCOT Tier-1 у обоих кодеков, миллисекунд на кадр:
| Задача | fvJPEG2000, мс | nvJPEG2000, мс | Кто быстрее |
|---|---|---|---|
| Кодирование 2K с потерями | 1,7 | 4,3 | fv, 2,5× |
| Кодирование 2K без потерь | 1,7 | 5,7 | fv, 3,4× |
| Кодирование 4K с потерями | 2,4 | 4,9 | fv, 2,0× |
| Кодирование 4K без потерь | 2,7 | 14,0 | fv, 5,2× |
| Декодирование 2K с потерями | 6,4 | 2,9 | nv, 2,2× |
| Декодирование 2K без потерь | 7,7 | 3,5 | nv, 2,2× |
| Декодирование 4K с потерями | 7,2 | 3,2 | nv, 2,3× |
| Декодирование 4K без потерь | 11,3 | 7,7 | nv, 1,5× |

При кодировании быстрее fvJPEG2000, при декодировании — nvJPEG2000
Весь выигрыш при кодировании даёт EBCOT Tier-1. Ядра fvJPEG2000 обрабатывают кадр быстрее в 2,0–5,2 раза, и на фоне этой стадии остальные занимают мало времени. Это именно та стадия, которая в таблице выше занимает от половины до трёх четвертей времени кадра.
При декодировании всё наоборот: Tier-1 у nvJPEG2000 быстрее в 1,5–2,3 раза. Скорости декодирования в разделе 7 сошлись не оттого, что декодер fvJPEG2000 считает быстрее, а оттого, что он плотнее укладывает работу во времени — об этом ниже.
Вейвлет-преобразование у nvJPEG2000 быстрее почти везде. При кодировании 2K с потерями — 41 микросекунда против 92 у fvJPEG2000, на 4K — 186 против 292; при декодировании — 70 против 155 и 254 против 394. Это ускорение от 1,6 до 2,2 раза. Но на полном времени сжатия кадра это сказывается мало: вейвлет занимает единицы процентов. Но результат измерен, и мы его приводим.
Второе, что видно снаружи, — простаивала ли видеокарта. Стадии одного кадра идут строго по очереди: вейвлет-преобразование не начать, пока не готово цветовое, а Tier-1 — пока не готов вейвлет. Поэтому, пока кадр обрабатывается один, на видеокарте в каждый момент выполняется одно ядро, а между ядрами остаются промежутки: следующее ядро ещё не запущено, данные ещё копируются. NVIDIA называет такие промежутки голоданием видеокарты (GPU starvation) — работы для выполнения у неё нет — и советует искать причину в коде на процессоре, который должен запускать следующую стадию работы алгоритма кодека. Долю времени, занятую работой ядер, они называют покрытием ядрами (kernel coverage), а голодание — это остаток до ста процентов.
Промежутки измеримы. В таблице — доли времени теста по всем шестнадцати задачам у обоих кодеков: какую часть времени на видеокарте выполнялось хотя бы одно или копирование.
| Режим | Покрытие ядрами | Ядра или копирования |
|---|---|---|
| Одиночные кадры | 42–86 % | 61–93 % |
| Лучшее сочетание потоков и пачки | 70–97 % | 93–104 % |
Значения чуть больше ста процентов получаются там, где запись профилировщика захватывает и прогрев, а длительность теста считается по скорости, которую вывела программа; расхождение доходит до 4 %.
В режиме одиночных кадров карта простаивает по-настоящему. В худшей точке fvJPEG2000 — кодирование 4K без потерь — ядра занимают 42 % времени, вместе с копированием 61 %, то есть 39 % времени видеокарта не делает вообще ничего и ждёт следующего задания.
Там же проверяется и цепочка стадий. Если поделить сумму времени работы ядер на покрытие, получится, сколько ядер шло одновременно в те моменты, когда карта была занята. В режиме одиночных кадров это единица во всех шестнадцати задачах — от 0,99 до 1,01, у обоих кодеков: пока кадр обрабатывается один, ядра не идут внахлёст нигде.
Заполнить промежутки можно только обработкой других кадров. У первого кадра идёт Tier-1, а у второго в это же время считается вейвлет. Видеокарта это умеет: ядра, поставленные в разные очереди команд, выполняются на ней одновременно, и NVIDIA называет это одновременным выполнением ядер (concurrent kernel execution).
Здесь важно различать два уровня, и они устроены по-разному.
Первый уровень — то, что происходит внутри одного ядра. Здесь распоряжается сама видеокарта: тысячи нитей она собирает в группы по 32 — такая группа называется варпом — и раздаёт их своим ядрам CUDA, счётным единицам внутри карты, которых на RTX 4090 шестнадцать тысяч. Кодек в это не вмешивается никак: он задаёт только размер задачи, а раскладку по счётным единицам карта делает сама.
Второй уровень — сколько ядер выполняется на видеокарте одновременно. Здесь всё наоборот: это целиком зависит от кода на процессоре — сколько кадров он держит в работе и в какие очереди команд их ставит. Число потоков и размер пачки, которые подбирались в разделах 6 и 7, — это именно про второй уровень. Дальше в разделе речь идёт только про него: первый уровень мы не измеряли.
Насколько это удалось, показывает сумма времени работы всех ядер за тест, делённая на длительность этого теста. Будем называть эту величину средней одновременностью ядер, AKC (Average Kernel Concurrency). Считается она так же, как средняя численность смены в цехе: в табеле записано, кто сколько отработал, складываем человеко-часы и делим на длительность смены. Получилась единица — всю смену работал один человек. Получилось 0,4 — работник был один и большую часть смены простоял без дела. Получилось три — в среднем одновременно работали трое. Здесь смена — это тест, работники — ядра, а табель ведёт профилировщик: он записывает каждое ядро и его время.
AKC для обоих кодеков в режиме одиночных кадров и при лучшем сочетании числа потоков и размера пачки:
| Задача | fvJPEG2000, одиночные кадры | fvJPEG2000, оптимум | nvJPEG2000, одиночные кадры | nvJPEG2000, оптимум |
|---|---|---|---|---|
| Кодирование 2K с потерями | 0,66 | 2,27 | 0,80 | 1,39 |
| Кодирование 2K без потерь | 0,57 | 1,58 | 0,78 | 1,21 |
| Кодирование 4K с потерями | 0,52 | 1,88 | 0,65 | 0,93 |
| Кодирование 4K без потерь | 0,42 | 1,69 | 0,76 | 0,90 |
| Декодирование 2K с потерями | 0,86 | 2,20 | 0,82 | 4,19 |
| Декодирование 2K без потерь | 0,84 | 2,40 | 0,80 | 2,09 |
| Декодирование 4K с потерями | 0,70 | 2,19 | 0,68 | 2,29 |
| Декодирование 4K без потерь | 0,65 | 1,78 | 0,70 | 1,21 |

Чем больше значение, тем больше ядер видеокарта загружала одновременно
В режиме одиночных кадров AKC меньше единицы у обоих кодеков, и это ожидаемо: кадры идут строго по одному, и заполнить промежутки нечем. В лучшем сочетании потоков и пачки картина расходится. При кодировании AKC у fvJPEG2000 доходит до 1,6–2,3, а у nvJPEG2000 остаётся около единицы — от 0,90 до 1,39, то есть на 4K видеокарта у nvJPEG2000 простаивает даже в оптимуме. Это и есть вторая причина разницы в скорости: в разделе 6 fvJPEG2000 кодировал в 3,9–6,6 раза быстрее, и теперь видно, что дело не только в скорости самих ядер, но и в том, сколько их удаётся занять одновременно.
При декодировании плотно укладывают работу оба кодека, а nvJPEG2000 на 2K с потерями доходит до AKC 4,19 — больше, чем любой из них где-либо ещё. Поэтому в разделе 7 скорости декодирования и сошлись: там разница между кодеками в лучшем сочетании составила от 0,3 до 8 % в обе стороны.
Устроено это у кодеков по-разному, и видно это по числу запусков ядра на кадр. У fvJPEG2000 при пачке из двух кадров главное ядро Tier-1 запускается 0,50 раза на кадр, при пачке из четырёх — 0,25 раза: один запуск обрабатывает сразу 2 или 4 кадра, работа укрупняется, а промежутков между запусками становится меньше. У nvJPEG2000 запусков ровно по одному на кадр — от 1,00 до 1,04 при любом числе кадров в работе: кадры в один запуск не объединяются, и занять карту можно только тем, чтобы подавать ей кадры из большего числа потоков процессора. Отсюда и разные оптимумы при кодировании: fvJPEG2000 хватает 8 потоков, а nvJPEG2000 нужно 16 или 32.
В лучшем сочетании промежутки заполняются, но не только вычислениями. Покрытие ядрами поднимается до 70–97 %, а вместе с копированием до 93–104 %: карта почти всё время чем-то занята, и то, что осталось между ядрами, — это в основном обмен по шине, а не ожидание работы. У nvJPEG2000 при кодировании 4K с потерями ядра занимают 70,2 % времени, а вместе с копированием 99,6 %: почти треть времени видеокарта не считает, а копирует. У fvJPEG2000 на той же задаче 76,5 % и 98,5 %.
Третье — шина. Профилировщик показывает и её загрузку: долю времени теста, в которой идёт копирование между оперативной памятью и памятью видеокарты. При кодировании 4K с потерями шина занята 66 % времени у fvJPEG2000 и 39 % у nvJPEG2000, при декодировании 4K без потерь — 21 % и 82 %. Скорость копирования при этом 22–26 ГБ/с — для PCIe 4.0 ×16 это очень высокая скорость. И на кадре 4K время копирования уже сравнимо со временем вычислений: значит выигрыш от дальнейшего ускорения кодека будет всё меньше, потому что рядом стоит время передачи данных, а оно не уменьшается.
Важные замечания.
Профилировщик замедляет тест, и замедляет неодинаково. Поэтому каждая точка измерялась дважды — с ним и без него, по скорости, которую печатает сама программа. На 22 точках из 32 расхождение уложилось в 2 %, на остальных дошло до 15,5 %. Значит AKC — оценка снизу: без профилировщика тот же тест идёт быстрее, а сумма времени работы ядер остаётся прежней, то есть одновременно работает больше ядер.
Доли стадий имеют смысл только для режима одиночных кадров. Когда кадры обрабатываются одновременно, сумма времени стадий больше времени кадра — именно потому, что стадии разных кадров считаются в одно и то же время. Складывать их и получать время кадра в этом режиме нельзя.
В одиночном и многопоточном декодировании измеряется разное. В режиме одиночных кадров копирование обработанного изображения из памяти видеокарты в оперативную в измеряемое время не входит, а в многопоточном входит; об этом сказано в разделе 4, и запись профилировщика это подтверждает — копирований из памяти видеокарты в оперативную в одиночном режиме нет ни одного. Сам алгоритм выполняется целиком в обоих режимах: Tier-2 при декодировании идёт на процессоре в начале работы и в измеряемое время входит всегда. При чтении таблицы раздела 7 это надо держать в уме: разница между её столбцами — не только разница режимов.
9. Контроль качества при сжатии JPEG2000
Как проверить, что кодеки сравнивали в одинаковых условиях?
Тремя проверками. В режиме без потерь декодированный кадр обязан побитно совпасть с исходным, и он совпал у обоих кодеков во всех четырёх сочетаниях. В режиме с потерями при равном размере файла сравнивается PSNR, и разница между кодеками там в десятых долях децибела. И измерения сделаны на сборках без ватермарка, программа это проверила.
Третье правило: одной скорости для сравнения недостаточно.
Измерение скорости без проверки результата ничего не гарантирует: декодер, который делает меньше работы, чем должен, выглядит быстрее. Поэтому при каждом запуске для каждого из восьми сочетаний условий выполняется полный цикл: изображение кодируется, декодируется и сравнивается с исходным.
Режим без потерь: точное совпадение. Все четыре сочетания (оба кодека, оба кадра) дали декодированное изображение, побитно равное исходному. Это обязательное условие: если бы хоть в одном случае совпадения не было, речь шла бы уже не о сжатии без потерь и сравнивать скорости было бы не с чем.
Режим с потерями: отношение сигнал-шум. Сравнение идёт при согласованном размере файла, поэтому таблица отвечает на вопрос у кого меньше искажений для одного и того же кадра.
| Изображение | fvJPEG2000, дБ | nvJPEG2000, дБ | Разница |
|---|---|---|---|
| 2K | 40,42 | 40,60 | 0,18 |
| 4K | 41,97 | 42,23 | 0,26 |
Разница в пользу nvJPEG2000, но она невелика. Для понимания порядка: разница в 1 дБ на фотографическом материале обычно уже неразличима на глаз, а десятые доли лежат в пределах того, что даёт выбор параметров внутри одного кодека. Так что качество на одном и том же файле у двух реализаций практически одинаковое, и это именно тот вывод, который был нужен: он подтверждает, что сравнение скоростей ведётся при сопоставимом результате, а не за счёт того, что один кодек экономит на качестве.
Про SSIM и MS-SSIM. Кроме отношения сигнал-шум есть структурные метрики — SSIM и его многомасштабный вариант MS-SSIM. Их вспоминают там, где отношение сигнал-шум плохо согласуется со зрительным восприятием: оно считает среднюю ошибку по всему кадру и не отличает ошибку, размазанную ровным шумом, от той же по величине ошибки, собранной на одной границе. Мы их посчитали на тех же кадрах, при тех же размерах файла и относительно того же оригинала.
| Изображение | SSIM, FV | SSIM, NV | MS-SSIM, FV | MS-SSIM, NV |
|---|---|---|---|---|
| 2K | 0,9824 | 0,9826 | 0,99676 | 0,99679 |
| 4K | 0,9827 | 0,9828 | 0,99649 | 0,99651 |
Порядок кодеков тот же, что по отношению сигнал-шум, а разница между ними находится в третьем-четвёртом знаке после запятой. Для такого уровня качества обе меры уже насыщены: они близки к единице, и места для разницы там почти не остаётся. То же самое говорит и первый процентиль карты SSIM — та её часть, где кадр восстановлен хуже всего: 0,941 против 0,942 на 2K. Поэтому в таблице выше стоит отношение сигнал-шум, потому что его проще проверить и пересчитать. Там, где качество ниже и разрыв между кодеками больше, смотреть надо уже на структурные метрики.
Про ватермарк. Демонстрационные сборки кодеков наносят на кадр ватермарк, и тогда сравнивать декодированный кадр напрямую с исходным файлом нельзя: измерялся бы ватермарк, а не кодек. Эти измерения сделаны на сборке без ватермарка, и программа это проверила: ватермарка не оказалось ни у одного из кодеков, поэтому PSNR посчитан прямо относительно оригинала.
На демонстрационной версии проверка качества тоже воспроизводится, и особая сборка для этого не нужна. Приём такой: эталоном для PSNR берётся не исходный файл, а кадр, вернувшийся через полный цикл без потерь на той же сборке. Ватермарк наносится до кодирования, режим без потерь сохраняет всё побитно, значит такой эталон и есть ровно то, что получил кодер, и PSNR меряет потери кодирования, а не ватермарк. Программа проверяет и само это условие: два независимых полных цикла без потерь обязаны совпасть байт в байт. Всё это уже заложено в скрипт и включается само.
10. Режим PCRD: фиксированный размер файла и скорость кодирования
Насколько режим PCRD замедляет кодирование?
Меньше всего — в 1,34 раза на 4K: это когда базовое качество подобрано заранее, на единицу выше того, при котором кадр сам даёт нужный размер, а PCRD только доводит размер файла до цели. Если квантование задано параметром q = 100 и весь коэффициент сжатия даёт PCRD, отставание больше: 1,56 раза на 4K и 1,84 на 2K. На одиночных кадрах отставание меньше, чем в многопоточном режиме: на 2K это 1,84 раза против 2,76.
В предыдущих разделах оба кодека работали одинаковым образом: мы задавали параметр качества, а размер сжатого файла получался как следствие. В реальной работе так бывает не всегда. Часто заранее известны пропускная способность канала или ёмкость носителя, и кадр нужно уложить в заданный размер: сжать ровно в двадцать раз или уместить в столько-то мегабайт.
Кодек fvJPEG2000 умеет это делать: в режиме PCRD задаётся нужный коэффициент сжатия, а кодер сам решает, какие младшие биты отбросить, чтобы этот коэффициент получить. У nvJPEG2000 такого режима нет, поэтому весь этот раздел — только про fvJPEG2000: сравнивать не с чем.
Квантование и режим PCRD работают последовательно, сначала квантование, потом PCRD. Таким образом, коэффициенты вейвлет-преобразования квантуются в соответствии с заданным параметром качества, а потом PCRD отбрасывает столько младших бит, сколько нужно, чтобы выйти на заданный коэффициент сжатия (раздел 3.2). Так обычно и поступают: базовое качество подбирают заранее, на кадрах того же типа, а параметр -cr задаёт итоговый размер файла.
Сначала посмотрим на результаты кодирования с PCRD при разных коэффициентах сжатия. Квантование здесь работает при q = 100, то есть оно относительно слабое, а итоговый коэффициент сжатия определяется в основном параметром -cr.
В таблице ниже только кодирование: режим PCRD работает на стороне кодера, декодер про него ничего не знает и просто разбирает готовый файл. Значения получены в режиме одиночных кадров: кадры обрабатываются по одному, без многопоточности и без пачки (раздел 4.1).
| Кадр | Коэффициент сжатия | Файл, кБ | Кодер, кадр/с |
|---|---|---|---|
| 2K | 5:1 | 1213 | 200 |
| 2K | 10:1 | 602 | 212 |
| 2K | 20:1 | 295 | 223 |
| 4K | 5:1 | 4662 | 128 |
| 4K | 10:1 | 2408 | 122 |
| 4K | 20:1 | 1182 | 130 |
Скорость кодирования почти не зависит от того, какой коэффициент сжатия установлен: на 4K это 128, 122 и 130 кадров в секунду при 5:1, 10:1 и 20:1. Так и должно быть, раз квантование остаётся на q = 100: кодировать приходится одно и то же количество данных, а коэффициент сжатия меняет только то, сколько младших бит отброшено после кодирования.
Посмотрим, насколько режим PCRD замедляет кодирование. Чтобы сравнение было корректным, у всех вариантов на выходе делаем файл одного и того же размера — тот, который можно получить при использовании коэффициента качества q = 85: 588 кБ на 2K и 1246 кБ на 4K. Именно с этим качеством мы работали в разделах 6–9. Один и тот же размер получен четырьмя способами: только квантованием без PCRD и ещё тремя, где нужный размер регулируется режимом PCRD и коэффициентами качества q = 90, 95 и 100.
Столбец «замедление» показывает, во сколько раз строка медленнее первой строки того же кадра — той, где PCRD выключен. Лучшее для строки сочетание потоков и пачки указано в скобках. Размеры файлов у всех строк совпадают с точностью до одной десятой процента, поэтому строки сравнимы между собой.
Все числа в таблице ниже получены в одном тесте, поэтому их можно делить друг на друга. Строки без PCRD — это тот же режим, в котором получена таблица раздела 6; измеренные здесь скорости отличаются от опубликованных там не более чем на 6 %, и все четыре — в меньшую сторону: 358,5 против 381 на одиночных кадрах 2K и 1879 против 1914 при лучшем сочетании потоков и пачки.
| Кадр | Качество q и режим |
Одиночные кадры | Замедление | Потоки и пачка | Замедление | PSNR, дБ |
|---|---|---|---|---|---|---|
| 2K | 85, без PCRD | 358,5 | — | 1879 (8×2) | — | 40,41 |
| 2K | 90 и PCRD | 211,5 | 1,70× | 823 (8×1) | 2,28× | 39,80 |
| 2K | 95 и PCRD | 201,5 | 1,78× | 755 (8×1) | 2,49× | 39,74 |
| 2K | 100 и PCRD | 194,6 | 1,84× | 681 (8×1) | 2,76× | 39,25 |
| 4K | 85, без PCRD | 187,2 | — | 614 (8×1) | — | 41,97 |
| 4K | 90 и PCRD | 134,1 | 1,40× | 400 (8×1) | 1,54× | 41,50 |
| 4K | 95 и PCRD | 129,1 | 1,45× | 362 (8×1) | 1,70× | 41,51 |
| 4K | 100 и PCRD | 120,1 | 1,56× | 314 (8×1) | 1,96× | 41,24 |
Насколько замедлится кодирование, зависит от того, задано ли квантование. Если квантование остаётся на q = 100 и итоговый коэффициент сжатия определяется режимом PCRD, кодер работает в 1,56 раза медленнее на 4K и в 1,84 раза медленнее на 2K. Если задать квантование заранее, скорость падает не так сильно: на 4K отставание сокращается с 1,56 до 1,40 раза. Возвращается примерно четверть-треть, но отставание будет в любом случае, поскольку остаётся стадия PCRD, а в первой строке её нет вовсе.
В многопоточном режиме отставание больше, чем на одиночных кадрах. На 2K это 1,84 раза по одиночным кадрам и 2,76 раза при лучшем сочетании потоков и пачки, на 4K — 1,56 и 1,96 раза. Разница существенная: если систему рассчитывают по общей пропускной способности, потери от режима PCRD окажутся заметно больше тех, что видны на одиночных кадрах.
При одном и том же размере файла режим PCRD также немного ухудшает качество изображения. На 2K PSNR равен 39,25 дБ против 40,41 дБ в строке без PCRD, на 4K — 41,24 против 41,97. Закономерность одна и та же во всех строках: чем ниже уровень качества, тем меньше данных попадает в энтропийный кодер, т.е. производительность сжатия увеличивается. q = 90 быстрее, чем q = 95, а q = 95 быстрее, чем q = 100. По PSNR оба сочетания дают почти одинаковый результат, и оба заметно лучше, чем один PCRD.
Осталось понять, какое качество ставить. В таблице выше самый выгодный вариант — q = 90, но между ним и q = 85 остаётся промежуток: при 85 кадр сам даёт нужный размер и PCRD резать нечего, при 90 естественный размер уже в полтора раза больше цели. Оптимум лежит где-то между ними, поэтому q = 86, 87 и 88 промерены отдельно.
Это отдельный прогон, поэтому абсолютные скорости в нём немного выше, чем в таблице выше: это другая сессия измерений. Сравнивать нужно отношения, и они сошлись: строка «100 и PCRD» дала здесь 1,79 раза на 2K и 1,54 раза на 4K против 1,84 и 1,56 в предыдущем прогоне. Все измерения в режиме одиночных кадров, размеры файлов сведены с точностью лучше одной десятой процента.
| Кадр | Качество q и режим |
Кодер, кадр/с | Замедление | PSNR, дБ |
|---|---|---|---|---|
| 2K | 85, без PCRD | 372,1 | — | 40,42 |
| 2K | 86 и PCRD | 228,4 | 1,63× | 40,40 |
| 2K | 87 и PCRD | 227,1 | 1,64× | 40,23 |
| 2K | 88 и PCRD | 230,3 | 1,62× | 39,98 |
| 2K | 100 и PCRD | 207,4 | 1,79× | 39,25 |
| 4K | 85, без PCRD | 194,4 | — | 41,97 |
| 4K | 86 и PCRD | 145,1 | 1,34× | 42,00 |
| 4K | 87 и PCRD | 142,7 | 1,36× | 41,89 |
| 4K | 88 и PCRD | 142,2 | 1,37× | 41,67 |
| 4K | 100 и PCRD | 126,6 | 1,54× | 41,24 |
Ближайшее сверху качество и оказывается лучшим. На 4K q = 86 даёт самое малое отставание из всех вариантов, 1,34 раза, и при этом PSNR 42,00 дБ, то есть не хуже, чем у файла того же размера, сжатого одним квантованием (41,97). На 2K скорость при 86, 87 и 88 одинакова в пределах процента, а PSNR падает: 40,40, 40,23 и 39,98 дБ. Значит и здесь брать надо ближайшее сверху.
Правило получается простое: базовое качество ставят на одну-две единицы выше того, при котором кадр сам даёт нужный размер. Тогда PCRD дорезает совсем немного, кодирование замедляется меньше всего, а качество изображения остаётся на уровне обычного квантования.
Откуда берётся отставание, видно по времени отдельных стадий кодирования. Параметр -info выводит это время для одного кадра (раздел 8), и там видны две разные составляющие. Первая — сама стадия PCRD: в первой строке её нет, в остальных она есть. Вторая — время EBCOT Tier-1: чем выше задано качество, тем больше данных доходит до энтропийного кодирования и тем дольше эта стадия работает.
Как и в разделе 8, здесь надо помнить: параметр -info синхронизирует стадии между собой, поэтому их сумма получается больше настоящего времени обработки кадра. Эти числа можно сравнивать между строками, но складывать значения в столбце нельзя.
4K, качество q и режим |
Tier-1, мс | PCRD, мс |
|---|---|---|
| 85, без PCRD | 3,37 | — |
| 90 и PCRD | 3,94 | 1,85 |
| 95 и PCRD | 4,25 | 1,81 |
| 100 и PCRD | 4,84 | 1,77 |
Во всех трёх строках, где стадия PCRD есть, она занимает примерно одно и то же время — около 1,8 мс. А время Tier-1 растёт по мере того, как квантование становится слабее: 3,37, 3,94, 4,25 и 4,84 мс. На 2K картина такая же: 2,76, 3,22, 3,41 и 3,70 мс, а стадия PCRD занимает от 1,5 до 1,7 мс. Файл у всех четырёх строк одного размера, различается только способ, которым он получен.
Почему в таблицах стоит q = 100, хотя параметр качества мы не задавали. Это проверено отдельно: если задать q = 100 явно и вместе с параметром -cr, получаются те же значения, что и без параметра качества: на 4K 120,6 против 120,1 кадра в секунду и PSNR 41,24 в обоих случаях. Значит, без параметра качества кодер квантует ровно так же, как при q = 100.
Что из этого следует. Если размер файла не задан жёстко и может меняться от кадра к кадру, выгоднее обойтись одним параметром качества: так и быстрее, и качество выше. Если размер задан исходя из пропускной способности канала, скорости записи носителя или требования заказчика, лучше сначала подобрать квантование, на одну-две единицы выше того, при котором кадр сам даёт нужный размер, а режим PCRD оставить для точной подгонки. Два способа получить один и тот же файл могут отличаться по скорости до 2,8 раза, поэтому режим работы лучше выбирать при проектировании системы, а не после того, как она собрана.
11. Энергия видеокарты на кадр и загрузка процессора при кодировании и декодировании

Сколько энергии уходит на один кадр?
Сразу о том, что здесь измерено, а что нет. Все джоули в этом разделе — это энергия видеокарты. Энергию, израсходованную центральным процессором, мы не измеряли вовсе: у карты есть собственный счётчик, у процессора в этом прогоне такого измерения не было. Про процессор в таблицах стоит другая величина — сколько его логических ядер занято в среднем, — и она измерена, но это доля процессора, а не ватты и не джоули. Кодеки нагружают процессор по-разному, и перевес не в одну сторону: на кодировании больше ядер занимает nvJPEG2000, на декодировании — fvJPEG2000. Значит энергия процессора, которой в этих числах нет, у двух кодеков разная, и таблицы каждого из них слегка приукрашивают: на кодировании — nvJPEG2000, на декодировании — fvJPEG2000. На сколько и что из этого следует — в конце раздела, и читать его стоит вместе с таблицами.
Считать удобнее в обратную сторону: ватты, доступные видеокарте, делённые на джоули на кадр, дают частоту кадров, которую позволяет заданный предел мощности. При пределе в 100 Вт fvJPEG2000 кодирует 4K с потерями с частотой около 280 кадров в секунду, nvJPEG2000 — около 83. Это пересчёт по джоулям на кадр, а не измерение производительности при таком пределе мощности.
Скорость отвечает на вопрос «сколько кадров обработает одна видеокарта». Потребление энергии видеокартой тоже имеет большое значение.
- Сколько карт поместится. Блок питания рассчитан на определённую мощность, и от потребления одной карты зависит, сколько их можно поставить в один корпус.
- Куда отводить выделяемое тепло. Каждый израсходованный джоуль превращается в тепло, и его надо куда-то отвести. В бортовом или встраиваемом корпусе это ограничение может наступить раньше, чем закончится доступная вычислительная мощность.
- Сколько проработает батарея. На дроне или переносной установке запас энергии конечен, и джоули на кадр прямо переводятся в число кадров.
- Сколько это стоит. В дата-центре киловатт-часы — это деньги, а к потреблению видеокарт добавляются и расходы на охлаждение.
Перевести одно в другое несложно. Джоули на кадр, умноженные на кадры в секунду, дают ватты; обратный пересчёт полезнее: доступные ватты, делённые на джоули на кадр, дают ограничение на скорость работы. Оценка в 280 и 83 кадра в секунду при ста ваттах получена именно так, и верна она, пока джоули на кадр не зависят от заданного предела мощности. Этого мы не проверяли: энергия измерялась, когда карта работала на 230 и 186 ваттах. Обычно при более низком пределе карта снижает частоту и тратит на кадр немного меньше, так что настоящее число может оказаться и выше; точное надо измерять с установленным пределом мощности.
Как измеряли энергию видеокарты. Мы измеряем энергию, израсходованную видеокартой, и относим её к одному кадру. Мощность для сравнения кодеков не годится: тот, кто берёт меньше ватт, но работает дольше, обходится дороже. В джоулях на кадр учтены как энергопотребление, так и время работы.
Почему среднее здесь имеет смысл. В каждом измерении подряд идут однотипные операции: один и тот же кадр кодируется тысячи раз с одними и теми же параметрами сжатия. Кадры не отличаются ни размером, ни содержимым, режим работы карты установившийся. Поэтому средняя энергия на кадр — это энергия любого отдельного кадра, а не смесь разных работ. Будь в потоке разные кадры и разные режимы, то же среднее скрывало бы различия между ними.
Измерителей два, и они независимы. Оба относятся к видеокарте и только к ней.
- Опрос мощности. Программа
nvidia-smi, идущая вместе с драйвером NVIDIA, сообщает текущее потребление карты в ваттах. Мы опрашиваем её десять раз в секунду, усредняем за прогон и умножаем на длительность. Способ простой, но между опросами короткие всплески теряются, и в счёт попадает всё время работы программы, включая запуск и подготовку буферов. - Накопительный счётчик энергии внутри карты. Карта сама ведёт учёт израсходованных миллиджоулей с момента загрузки драйвера — это готовая сумма, между двумя чтениями не теряется ничего. Значение читается через NVML, программный интерфейс NVIDIA для управления видеокартой (
nvmlDeviceGetTotalEnergyConsumption). Счётчик есть не у всех моделей; на RTX 4090 он есть.
Разностный метод устроен так. Счётчик читается снаружи программы: показание берётся до запуска и после, поэтому в него попадают и постоянные расходы — запуск процесса, подготовка буферов, выход карты на режим. Чтобы их убрать, каждая точка измеряется дважды, на N кадрах и на 2N, и энергия кадра берётся как разность, делённая на N: всё, что не зависит от числа кадров, из разности выпадает.
Вывод для тех, кто будет повторять: длинного прогона достаточно, разность двух прогонов ничего заметного не добавляет и стоит вдвое дороже по времени. Правильнее измерять окно внутри самого цикла, то есть открывать счёт после сотни кадров, когда карта уже вышла на режим, но для этого программа должна читать счётчик сама. Мы это сделаем в следующей серии тестов.
Оба измерителя дали одно и то же: расхождение 2 % на медианной точке и 10 % в худшей. В таблицах ниже — показания счётчика, посчитанные разностным способом.
В обеих таблицах у каждого кодека взяты число потоков и размер пачки, дающие лучшую скорость. Предел мощности карты — 450 ватт. Последний столбец — сколько логических ядер центрального процессора занято в среднем; те же числа стоят в нижних строках таблиц разделов 6 и 7. Это загрузка, а не мощность: перевести её в ватты нечем, см. конец раздела.
Почему произведение не сходится с колонкой мощности. Джоули на кадр, умноженные на кадры в секунду из разделов 6 и 7, дают не то число, что стоит в столбце «Мощность карты». В большинстве строк расхождение не больше 4 %; у декодера fvJPEG2000 оно доходит до 6 % на 2K без потерь и до 10 и 16 % на 4K, а на кодировании 4K с потерями произведение оказывается на 5 % ниже. Причин две. Энергия на кадр посчитана разностным способом, и постоянные расходы из неё убраны, а мощность — это среднее за весь запуск, вместе с ними. И скорость взята из другого прогона, не из того, в котором измеряли энергию. Поэтому столбцы читаются по отдельности: кодеки сравниваются по джоулям на кадр, а мощность показывает уровень, на котором работала карта.
Кодирование
| Кадр | Режим | Кодек | Дж/кадр, видеокарта |
Мощность карты, Вт |
Логических ядер |
|---|---|---|---|---|---|
| 2K | с потерями | fvJPEG2000 | 0,122 | 230 | 7,0 |
| 2K | с потерями | nvJPEG2000 | 0,523 | 156 | 29,5 |
| 2K | без потерь | fvJPEG2000 | 0,224 | 258 | 7,2 |
| 2K | без потерь | nvJPEG2000 | 1,373 | 249 | 29,8 |
| 4K | с потерями | fvJPEG2000 | 0,355 | 230 | 7,5 |
| 4K | с потерями | nvJPEG2000 | 1,199 | 186 | 14,7 |
| 4K | без потерь | fvJPEG2000 | 0,729 | 264 | 7,6 |
| 4K | без потерь | nvJPEG2000 | 4,195 | 259 | 14,8 |
Декодирование
| Кадр | Режим | Кодек | Дж/кадр, видеокарта |
Мощность карты, Вт |
Логических ядер |
|---|---|---|---|---|---|
| 2K | с потерями | fvJPEG2000 | 0,178 | 177 | 7,6 |
| 2K | с потерями | nvJPEG2000 | 0,254 | 255 | 4,1 |
| 2K | без потерь | fvJPEG2000 | 0,435 | 179 | 28,6 |
| 2K | без потерь | nvJPEG2000 | 0,794 | 343 | 3,5 |
| 4K | с потерями | fvJPEG2000 | 0,517 | 176 | 25,9 |
| 4K | с потерями | nvJPEG2000 | 0,653 | 279 | 4,8 |
| 4K | без потерь | fvJPEG2000 | 1,384 | 182 | 27,8 |
| 4K | без потерь | nvJPEG2000 | 2,471 | 325 | 3,4 |
Из таблиц видно что при кодировании nvJPEG2000 берёт меньше ватт, 156 против 230 на 2K с потерями, но кадров за них выдаёт в шесть с половиной раз меньше, и кадр обходится ему дороже в 4,3 раза; по всем четырём задачам разрыв от 3,4 до 6,1 раза. При декодировании скорости почти равны, а карта работает по-разному: 176–182 ватта у fvJPEG2000 против 255–343 у nvJPEG2000, и кадр обходится fvJPEG2000 в 1,3–1,8 раза дешевле.
Энергопотребление процессора
Оно не измерено, и это надо сказать прямо. У видеокарты есть встроенный счётчик израсходованных джоулей, и он относится только к ней. У центрального процессора такого измерения в этом прогоне не делалось, поэтому джоулей на кадр по процессору в статье нет и быть не может. Всё, что мы про него знаем, — это загрузка: сколько логических ядер в среднем занимал сам кодек. Величина измерена корректно (берётся процессорное время самого процесса кодека, пользовательское плюс системное, и делится на время его работы: израсходовал процесс семьдесят секунд процессорного времени за десять секунд работы — значит в среднем занято семь логических ядер; посторонняя нагрузка на машину в эту величину не входит), но в ватты она не переводится: одно занятое ядро на разной работе тратит разную мощность.
Почему это не мелочь. Загрузка процессора у двух кодеков отличается в разы, и притом в разные стороны на кодировании и на декодировании. Значит недостающая часть — энергия процессора — у них не одинакова и при сравнении не сократится: она сдвигает выводы, и надо понимать, в какую сторону.
На кодировании больше логических ядер процессора занимает nvJPEG2000. Он занимает 29,5 и 29,8 логического ядра на 2K и 14,7 и 14,8 на 4K, тогда как fvJPEG2000 — от 7,0 до 7,6 на всех четырёх задачах. То есть по процессору nvJPEG2000 тратит вдвое-вчетверо больше, и эта работа в джоули не попала. Разрыв по видеокарте — от 3,4 до 6,1 раза; если бы считали всю систему, он стал бы больше, а не меньше.
На декодировании больше логических ядер процессора занимает уже fvJPEG2000. Здесь картина обратная: nvJPEG2000 занимает от 3,4 до 4,8 логического ядра, а fvJPEG2000 — 28,6 на 2K без потерь, 25,9 и 27,8 на 4K, и только на 2K с потерями остаётся на 7,6. Причина — в том, где оказался оптимум: на трёх задачах из четырёх лучшая точка декодера fvJPEG2000 ушла на тридцать два потока, а тридцать два логических ядра — это вся машина. Поэтому «кадр обходится fvJPEG2000 в 1,3–1,8 раза дешевле» — это утверждение про видеокарту, а не про систему целиком. Сохранится ли оно, если считать вместе с процессором, по этим измерениям сказать нельзя.
И это не обязательная плата. Речь дальше только о декодере fvJPEG2000: это у него оптимум ушёл на тридцать два потока, и дают они считанные проценты скорости. Вот три его задачи из четырёх, где так вышло, рядом с более экономной точкой той же сетки — числа из тех же прогонов раздела 7.
| Задача, декодер fvJPEG2000 | Оптимум | Кадров в секунду | Логических ядер | Точка 16×2 | Логических ядер | Цена отказа |
|---|---|---|---|---|---|---|
| 2K, без потерь | 32×2 | 436,2 | 26,6 | 424,6 | 14,2 | 2,7 % |
| 4K, с потерями | 32×2 | 394,5 | 21,6 | 377,2 | 13,4 | 4,6 % |
| 4K, без потерь | 32×1 | 145,1 | 26,2 | 139,9 | 13,4 | 3,7 % |
Три-пять процентов скорости стоят вдвое большего числа занятых ядер. Числа даны с десятыми, чтобы проценты в последнем столбце сходились: по округлённым до целых они вышли бы на десятую долю процента меньше. А на 2K без потерь есть точка ещё выгоднее: 8×4 даёт 425,4 кадра в секунду при 7,4 логического ядра — та же скорость, что у 16×2, вдвое меньше занятого процессора, чем у неё, и в три с половиной раза меньше, чем в оптимуме, при отставании от него на 2,5 %. Так что если процессор в системе нужен и на что-то ещё, эти проценты стоит отдать и взять точку поменьше; заодно и вопрос о недостающей энергии процессора становится куда менее острым.
Как это измерить, если понадобится. У современных процессоров есть встроенный счётчик израсходованной энергии всего кристалла — у Intel он называется RAPL, у AMD устроен так же. Он даёт энергию процессора целиком, а не отдельной программы, поэтому делать измерения с его помощью нужно на спокойной машине и с той же разностью двух прогонов, что и у карты: показание до запуска и после, дважды, на N и на 2N кадрах. На Linux счётчик доступен штатно, на Windows нужен сторонний драйвер, и это отдельная работа, а не строчка в скрипте. В следующей серии измерений мы это сделаем, и тогда джоули на кадр можно будет привести и по системе в целом.
И ещё один ресурс — память видеокарты. Скорость в лучшей точке достаётся не даром: каждый кадр, находящийся в работе одновременно, занимает место в памяти карты. Мы сделали такие измерения для обеих сторон, снаружи, по самой карте: наибольшее занятое место за время запуска минус то, что было занято до него.
| Задача | Точка FV | Кадров в работе | FV, ГБ | Точка NV | Кадров в работе | NV, ГБ |
|---|---|---|---|---|---|---|
| Кодирование 2K, с потерями | 8×2 | 16 | 2,5 | 32×2 | 64 | 6,8 |
| Кодирование 2K, без потерь | 8×2 | 16 | 2,5 | 32×2 | 64 | 6,8 |
| Кодирование 4K, с потерями | 8×1 | 8 | 4,7 | 16×2 | 32 | 12,8 |
| Кодирование 4K, без потерь | 8×1 | 8 | 4,6 | 16×2 | 32 | 12,8 |
| Декодирование 2K, с потерями | 8×4 | 32 | 2,5 | 8×4 | 32 | 2,1 |
| Декодирование 2K, без потерь | 32×2 | 64 | 4,0 | 8×4 | 32 | 2,2 |
| Декодирование 4K, с потерями | 32×2 | 64 | 11,6 | 8×4 | 32 | 7,2 |
| Декодирование 4K, без потерь | 32×1 | 32 | 6,8 | 8×4 | 32 | 7,5 |
На один кадр в работе обе стороны тратят сопоставимо: при кодировании у fvJPEG2000 это 25–27 размеров несжатого кадра, у nvJPEG2000 — 17–18, при декодировании обе укладываются в 8–14. Разница итоге берётся не отсюда, а из того, что лучшие точки у кодеков разные. nvJPEG2000 выходит на свой максимум, когда в работе 64 кадра, а fvJPEG2000 — когда 16, и поэтому на кодировании 2K занимает 6,8 ГБ против 2,5, а на 4K — 12,8 против 4,7. На декодировании то же правило работает в другую сторону: там к 32 потокам ушёл декодер fvJPEG2000, и на 4K с потерями он занимает 11,6 ГБ против 7,2 — за ту самую прибавку в скорости, цена которой разобрана выше. Занятые ядра и занятая память — это два критерия занятости системы.
Отсюда же и прочерк в строке 32×2 у кадра 4K в разделе 6. Восемь кадров в работе стоят кодеру fvJPEG2000 4,7 ГБ; шестьдесят четыре потребовали бы около 37 ГБ, а на карте есть 22 ГБ.
Как это измерено и чему тут верить. Полученные значения верны для всей карты, а не для отдельного процесса: на Windows память по процессам не отдаётся. Значит в каждое число входит и постоянная часть, контекст CUDA, около полугигабайта, и верны они при условии, что во время замера на карте не было ничего постороннего.
12. Что это значит на практике: латентность и пропускная способность
Здесь результаты измерений переводятся в решение — что выбрать под свою задачу.
Кодирование — быстрее fvJPEG2000, во всех восьми вариантах условий тестирования: быстрее в 3,9–6,6 раза по пропускной способности, и в 1,5–2,5 раза по латентности одного кадра. Основная причина в том, что кодер nvJPEG2000 почти не ускоряется от многопоточности; вдобавок он занимает вдвое-вчетверо больше логических ядер процессора.
Декодирование — кодеки показывают сравнимые результаты при лучшем сочетании потоков и пачки, а в режиме одиночных кадров nvJPEG2000 быстрее в 1,5–2,1 раза. Зато он заметно экономнее по процессору: три-пять логических ядер против семи у fvJPEG2000, а в трёх задачах из четырёх — против двадцати шести и более.
Качество при равном размере файла одинаковое — разница по PSNR в пределах трёх десятых децибела в пользу nvJPEG2000, на глаз неразличима.
Энергия на кадр лучше у fvJPEG2000 в 3,4—6,1 раз и повторяет картину по скорости, на декодировании при почти равной скорости кадр обходится для fvJPEG2000 в 1,3—1,8 раза дешевле.
Дальше эти выводы стоит перевести на язык задач, потому что в разных приложениях требования к кодеру и декодеру могут сильно отличаться.
Сначала о том, где необходимо кодирование. Это приложения для камер, в том числе для встраиваемых систем: данные приходят от камер, их надо сжимать сразу и не медленнее заданной частоты кадров. Во всех вариантах кодирования fvJPEG2000 впереди.
- Камерные и промышленные конвейеры. Поток с сенсора идёт через алгоритмы преобразований и потом попадает в JPEG2000, без записи промежуточных кадров.
- Спутниковая и аэрофотосъёмка. На борту сжимают, потом передают, на земле декодируют. Кодер работает там, где ограничены питание, вес и канал связи, а значит цена кадра в джоулях и производительность одной видеокарты также имеют большое значение.
- Оцифровка киноплёнки и мастеринг цифровых копий (DCP). Тысячи кадров подряд, каждый в JPEG2000 без потерь или с высоким качеством; выигрывает тот, кто быстрее обработает весь материал.
- Микроскопия и медицинская съёмка. Кадры большие, съёмка непрерывная, а разрешение всё растёт.
Теперь о том, где нужно именно декодирование. Это приложения для просмотра и обработки готового материала.
- Воспроизведение цифровых копий и мастер-материала. Поток кадров надо декодировать в реальном времени, без пропусков и тут нужно выбирать между режимами с максимальной скоростью и с минимальной латентностью.
- Разбор архивов. Терабайты уже сжатого материала, и на этой задаче бесплатная библиотека NVIDIA должна работать очень хорошо, но для этого её нужно запускать по схеме Fastvideo.
- Просмотр и выборочная выдача снимков — спутниковых, медицинских, картографических: пользователь открывает кадр и ждёт, поэтому время декодирования и вывода на монитор одного кадра имеет значение.
И ещё один момент про заданный размер файла. Если конвейер обязан попадать в заданную пропускную способность канала или носителя, это отражается на скорости: режим PCRD у fvJPEG2000 работает медленнее, чем фиксированный параметр качества, в 1,3–1,8 раза на одиночных кадрах и в 1,5–2,8 раза при лучшем сочетании потоков и пачки. Меньшее отставание — когда базовое качество задано, а PCRD только доводит размер; большее — когда всю работу делает один PCRD (раздел 10). Это надо закладывать в расчёт сразу, а не выяснять на готовой системе.
Все результаты выше получены на двух обычных фотографических кадрах. На вашем материале (с шумом, с текстом, с медицинской или спутниковой спецификой) соотношения могут быть другими. Пришлите свои кадры: пропустим их через оба кодека по этой же процедуре и вернём таблицу и снимки после кодирования/декодирования, чтобы вывод был ваш, а не наш.
13. Как повторить измерения у себя и получить максимум производительности
Это раздел, ради которого написано всё остальное: методика чего-то стоит только тогда, когда её можно запустить у себя.
Все измерения выполняет один скрипт на Python, который сам собирает программу для тестирования nvJPEG2000, готовит эталонные файлы, подбирает качество под одинаковый размер файла, выполняет кодирование и декодирование, печатает таблицу. Исходные изображения выложены на сайте. Никаких скрытых шагов в процедуре нет: вывод каждого запуска вместе с полной командной строкой сохраняется в лог, и любой результат можно воспроизвести вручную.
Полный запуск с тремя повторами на точку занимает около часа. Число кадров в каждом тесте скрипт подбирает сам, чтобы измерение на каждой точке длилось одинаково: быстрая точка не вырождается в доли секунды, а медленная не растягивается непомерно.
Заодно такой запуск показывает, какое сочетание числа потоков и размера пачки даёт максимум на вашей машине. В измерениях этой статьи лучшее сочетание менялось и от размера кадра, и от режима сжатия. Как оно ищется, описано в разделе 4.3, поэтому брать чужой оптимум готовым не стоит: дешевле найти свой тем же перебором.
Что входит в измеряемое время, можно не принимать на веру. Измерительная часть Fastvideo SDK поставляется исходниками, программа для nvJPEG2000 лежит в открытом репозитории Fastvideo (bench/nvj2k_bench-02/nvj2k_bench-02.cpp), примеры NVIDIA — в CUDALibrarySamples под лицензией BSD. Границы отсчёта в разделах 4.2 и 4.2.1 описаны по этим файлам, и каждую можно найти в них глазами.
Всё это лежит в открытом доступе: скрипт, программа для тестирования, результаты и логи. Где именно и как этим пользоваться — следующий раздел.
14. Открытый проект на GitHub: код и данные измерений для теста кодеков JPEG2000
Скрипт и программа для тестирования выложены на GitHub: github.com/fastvideo/jpeg2000-benchmark
Зачем он нужен. Любое сравнение кодеков, опубликованное одной из сторон, законно вызывает вопрос: а не подобраны ли условия? Ответ на такой вопрос — не заверения, а возможность взять процедуру и повторить её самому, на своей видеокарте и на своих изображениях, проверить исходники. Результаты в статье получены для выбранных изображений и для одной видеокарты. Репозиторий — это способ получить результаты своих тестов.
Вторая причина проще: методику неудобно пересказывать. Гораздо нагляднее показать её кодом, где видно каждое решение — какие ключи выставлены, что входит во время, а что нет, как именно подбирается качество и как считается проверка качества.
Что там лежит сейчас:
- скрипт, который выполняет все этапы измерений, описанные в этой статье;
- исходный код программы для тестирования nvJPEG2000 — той самой, чьё правило отсчёта времени разобрано в разделе 4.2;
- результаты каждой серии измерений: готовые таблицы, те же данные в машиночитаемом виде и сырые логи каждого запуска;
- эта статья целиком рядом с результатами, к которым она относится;
- ссылки на исходные изображения;
- короткий README: что это, как запустить, что получится.
Почему статья лежит и там тоже. Репозиторий — самостоятельный вход: сюда приходят из поиска, отсюда делают форк, копию уносят на машину без интернета. Репозиторий, в котором нельзя прочитать процедуру, не открыв браузер и не найдя нужную страницу, работает наполовину. При этом текст остаётся один: в репозитории лежит снимок статьи с датой и ссылкой на оригинал, и правится он только выгрузкой из оригинала, а не руками. Снимок привязан к папке результатов той же серии измерений. Поэтому в любой момент времени будет видно, по какой именно редакции методики получены эти результаты.
Что просится туда дальше — по мере готовности новых измерений, без сроков:
- OpenJPEG третьим участником сравнения: процессорная реализация, с которой обычно сравнивают всех остальных, и она открытая;
- результаты на других видеокартах и на другой разрядности по мере их получения;
- отдельная страница с описанием того, что именно менялось между сериями измерений: версия драйвера, версия библиотеки, версия кодека.
Как этим пользоваться. Проще всего сделать свою копию репозитория (форк) и провести измерения у себя: чужие результаты в такой теме стоят меньше, чем собственные. nvJPEG2000 бесплатна и скачивается с сайта NVIDIA. Кодек fvJPEG2000 запускается так:
- скорость воспроизводится на демонстрационной версии SDK, она свободно скачивается, ссылка в репозитории;
- проверка качества тоже воспроизводится на демонстрационной версии: эталоном для PSNR скрипт берёт кадр после полного цикла без потерь на той же сборке, как описано в разделе 9, и ватермарк в расчёт не попадает;
- сборка без ватермарка нужна только тем, кто хочет сравнивать декодированный кадр прямо с исходным файлом. Она выдаётся по запросу — напишите нам, и мы её пришлём.
Лицензия на скрипт и программу для тестирования разрешительная, а единственный ожидаемый способ участия здесь — форк. Библиотеки SDK идут под своей лицензией, это оговорено отдельно.
Замечания к процедуре — в разделе issues репозитория: ошибку в методике полезнее найти до публикации следующих чисел, чем после.
15. Что осталось непроверенным
Список ведётся открыто, потому что это часть методики: читатель должен видеть, где вывод обоснован измерением, а где рассуждением. Пять пунктов из девяти закрыты измерениями, четыре остаются.
Закрыто.
- Каждый декодер разбирал файл своего кодера. Это была главная угроза выводу: файлы одного размера не обязательно одинаковой внутренней сложности, и сравнение декодеров могло на деле оказаться сравнением того, что выдали кодеры. Проведено перекрёстное декодирование по всем восьми сочетаниям условий: разница нигде не превысила 1,4 %, а в шести случаях из восьми она меньше половины процента. Вывод о декодерах устоял.
- Контроль качества по всем восьми сочетаниям условий. Сделан, результаты в разделе 9: при сжатии без потерь — точное совпадение у обоих кодеков, при сжатии с потерями разница по PSNR в пределах трёх десятых децибела.
- Совпавшее значение Q. Проверялось то, ради чего пункт и был сделан: не след ли это самой процедуры подбора. Подбор запускался из двух разных начальных интервалов и на трёх уровнях качества: найденные значения от выбора интервала почти не зависят, то есть процедура ничего от себя не добавляет. Шкалы двух кодеков при этом очень похожи, но не совпадают: между двумя кадрами найденные эквиваленты расходятся на 0,07–0,12. Точного соответствия шкал мы не устанавливали, это отдельная задача. Подробности в разделе 3.3.
- Скорость режима PCRD при заданном квантовании. Измерена отдельным прогоном, результаты в разделе 10. При одном и том же размере файла один PCRD замедляет кодирование в 1,5–1,8 раза на одиночных кадрах и в 2,0–2,8 раза при лучшем сочетании потоков и пачки; подобранное базовое качество сокращает отставание до 1,3–1,6 раза на одиночных кадрах и одновременно даёт лучший PSNR.
- Профилирование кодера nvJPEG2000. Сделано профилировщиком NVIDIA Nsight Systems, результаты в разделе 8. Вывод о том, что кодер почти не ускоряется от многопоточности, был сделан по внешним признакам; теперь он подтверждён прямо: в лучшем сочетании потоков и пачки AKC у nvJPEG2000 остаётся 0,90–1,39, то есть на 4K видеокарта простаивает даже в оптимуме, а главное ядро запускается 1,00–1,04 раза на кадр — кадры в один запуск не объединяются.
Осталось.
- Субдискретизация цветности. Всё сравнение идёт на 4:4:4. Кодек NVIDIA принимает компоненты, уже приведённые к нужному размеру, поэтому прореживание пришлось бы делать сторонним кодом, и в измеряемое время попало бы время этого фильтра, а не работа кодеков. Такое сравнение имеет смысл делать, но отдельно и с явно оговорённым фильтром; мы этого не делали.
- Формат вывода декодера. Программа для тестирования nvJPEG2000 оставляет результат в виде раздельных плоскостей. Если декодер fvJPEG2000 внутри измеряемого отрезка собирает их в стандартный RGB, это работа, которую делает только одна сторона. Разбивка по этапам показала, что обратные преобразования (MCT и сдвиг уровня) занимают у декодера fvJPEG2000 около пяти процентов времени. Это меньше разрыва между кодеками, то есть на вывод не влияет, но при точном сопоставлении эту поправку стоит держать в уме.
- Отчего у одной точки два устойчивых состояния. Декодирование nvJPEG2000, 2K с потерями, 8×1 даёт то 309 кадров в секунду, то 539; состояние выбирается при старте и держится весь запуск. Дело в процессорной части: в медленном состоянии на кадр уходит на 45 % больше процессорного времени, а видеокарта работает на той же частоте. Размещение потоков по ядрам и способ ожидания видеокарты проверены и отпали; раздвоение зависит от числа потоков — при 8 и 16 оно есть, при остальных нет. Это единственный такой результат измерений в статье: остальные двадцать три точки декодирования повторяются в пределах пары процентов.
- Где у кодера nvJPEG2000 выполняется Tier-2. Документация NVIDIA называет процессорный этап только у декодера, а про кодер говорит лишь, что используются и видеокарта, и процессор.
16. Что дальше
Эта статья — первый шаг, а не итог. Ниже то, что запланировано дальше: сначала измерения, потом место, где всё это живёт постоянно.
Ближайшие измерения.
| Тема | Что даёт |
|---|---|
| 12 и 16 бит, монохром | именно там живут медицинские снимки и спутниковые кадры |
| Другие карты | RTX 5090, профессиональные и серверные |
| Jetson | тот же кодек на встраиваемой платформе |
| Кадры 8K и многотайловые | там, где кадр перестаёт помещаться в память целиком |
Первые две темы уже понятны по постановке и, скорее всего, станут отдельными статьями. На 12 и 16 битах меняется не только объём данных, но и сам материал: медицинские снимки и спутниковые кадры устроены иначе, чем фотографические сцены. Переносить на них результаты этой серии нельзя: в измерениях их не было. По Jetson черновик уже написан. Там главной величиной становится не скорость, а энергия на кадр, и результаты с настольной карты туда не переносятся даже в виде соотношений.
Открытый проект. Всё, что нужно для повторения, лежит в репозитории jpeg2000-benchmark — он описан в разделе 14. Туда же попадут новые измерения из списка выше, вместе с условиями и датами.
Права на этот материал
Текст статьи — под лицензией CC BY-ND 4.0: перепечатывать целиком и цитировать можно, в том числе в коммерческих изданиях, со ссылкой на источник; переписывать и переводить — по согласованию с нами, обычно мы не против. Причина ограничения простая: переписанное описание процедуры, ходящее под именем Fastvideo, вредит и читателю, и самим измерениям.
Результаты измерений и таблицы — под лицензией CC BY 4.0, без этого ограничения: их можно переносить в свои материалы, пересобирать и считать на их основе. Если что-то изменили — скажите, что именно.
Ссылка на источник в обоих случаях: Fastvideo, <адрес статьи>, измерения от <дата>. Просьба приводить условия измерений рядом с результатами: без них результат не воспроизводится, а результат, переживший свои условия, хуже, чем его отсутствие.
На изображения ни одна из этих лицензий не распространяется.
Приложение. Штатные программы NVIDIA и схема запуска Fastvideo
В приложении две темы. Сначала — четыре наблюдения о библиотеке nvJPEG2000 из открытых источников. Потом — измерение, которое отвечает на вопрос, поднятый этими наблюдениями: не занижена ли скорость библиотеки NVIDIA там, где её запускает Fastvideo.
Что известно о кодере nvJPEG2000 из открытых источников
Ниже четыре наблюдения из открытых источников. Первое — прямая цитата из документации, остальные три косвенные; все они согласуются с результатами измерений.
Документация NVIDIA называет процессорный этап только у декодера. Про декодер сказано прямо: Tier-2 выполняется на центральном процессоре, все остальные этапы вынесены на видеокарту. Про кодер — только то, что библиотека использует и видеокарту, и процессор, и что сжатое изображение записывается в оперативную память; какая часть работы достаётся процессору, не сказано (документация nvJPEG2000).
В наборе примеров NVIDIA есть конвейерный пример декодирования и нет конвейерного примера кодирования. Пример nvJPEG2000-Decoder-Pipelined показывает декодирование через несколько очередей на видеокарте (CUDA Stream). Штатный пример кодера обрабатывает кадры строго по очереди: одна очередь на видеокарте, одно состояние кодера, синхронизация после каждого кадра.
В интерфейсе библиотеки нет ни одной функции, принимающей массив изображений. Ни для кодирования, ни для декодирования: только nvjpeg2kEncode и nvjpeg2kDecodeImage на один кадр. Проверено по заголовочному файлу, а не по документации.
В примерах самой NVIDIA декодеру уделено заметно больше внимания, чем кодеру. В официальном наборе примеров CUDALibrarySamples для nvJPEG2000 лежат три примера на декодирование (обычный, конвейерный nvJPEG2000-Decoder-Pipelined и частичное декодирование тайлов) и один на кодирование, обычный (примеры NVIDIA CUDALibrarySamples). Конвейерного примера для кодера нет. Это косвенный признак, но он согласуется со всем остальным, что видно в измерениях.
Отдельно стоит заметить, какие результаты NVIDIA публикует сама. В открытых материалах есть измерения по декодированию — например, в записи блога про библиотеку nvImageCodec, где разбирается ускорение декодирования медицинских снимков: там приводятся и модели видеокарт, и размеры изображений, и сравнение с процессорной реализацией (блог NVIDIA о декодировании медицинских снимков). Опубликованных результатов по кодированию JPEG2000 найти не удалось ни в документации библиотеки, ни в блоге. Это наблюдение, а не упрёк: возможно, они просто не публиковались.
Почему результаты этой статьи нельзя сравнивать с бенчмарками из блога NVIDIA
В записи от 24 июня 2021 года есть график скорости декодирования кадра 1920 × 1080, 8 бит, 4:4:4, без потерь, вейвлет 5/3 (блог NVIDIA о декодировании JPEG2000 в цифровой патологии). Разрешение, разрядность и вейвлет там те же, что в разделе 7, но этого мало, чтобы числа можно было сравнивать.
График получен для версии библиотеки 0.2 — с тех пор прошло пять лет. Изображения другие, а скорость декодирования без потерь определяется содержимым кадра: сколько бит есть в исходных данных, столько декодер и разбирает. Параметры, с которыми эти изображения были сжаты, — размер кодоблока, число уровней разложения, порядок прогрессии — в записи не названы, а декодер работает с теми параметрами, которые использовались при кодировании. И граница отсчёта у NVIDIA другая: разбор сжатого изображения на процессоре в их измерение не входит, а в измерения этой статьи входит (раздел 4.2.1). Поэтому сравнение с тем графиком здесь не приводится.
Проверить, не занижена ли производительность библиотеки можно измерением на одном стенде, в один день, на одних файлах. Это и сделано дальше.
Штатные программы NVIDIA против схемы запуска Fastvideo
Во всех трёх столбцах кодирует одна и та же библиотека — nvJPEG2000 от NVIDIA. Разными бывают только две вещи: какая программа её вызывает и как эта программа подаёт кадры. Слева — пример из набора CUDALibrarySamples: программа, которую NVIDIA написала к своей библиотеке и выложила сама, взята без изменений. В середине — программа измерений Fastvideo, которая вызывает ту же библиотеку NVIDIA и так же обрабатывает кадры по очереди; от левого столбца она отличается только кодом вокруг nvJPEG2000: чтением файла, своим счётчиком времени, порядком вызовов. Справа — та же программа Fastvideo и та же библиотека NVIDIA, но кадры идут в нескольких потоках и находятся в работе одновременно.
Средний столбец проверяет не кодек, а программу измерений. Если бы код Fastvideo для библиотеки NVIDIA мешал ей работать в полную силу, средний столбец оказался бы ниже левого — и тогда правый столбец ничего бы не доказывал: выигрыш можно было бы объяснить тем, что библиотеке NVIDIA просто не дали показать себя.
Тесты для примера NVIDIA были сделаны в тех режимах работы, которые он допускает; в таблицу взят лучший его результат. В любом из этих режимов он обрабатывает кадры строго по очереди: одно состояние кодека, одна очередь заданий на видеокарте, синхронизация после каждого шага.
Обоим кодерам дана одна и та же работа, и это проверено точно. Штатный кодер NVIDIA, запущенный с параметрами раздела 3.1, дал файлы размером в 601 940, 2 966 036, 1 274 517 и 8 964 924 байт — ровно столько же, сколько программа измерений Fastvideo.
Кодирование, кадров в секунду
| Кадр и режим | Пример NVIDIA | Программа Fastvideo | Программа Fastvideo |
|---|---|---|---|
| кадры по очереди | кадры по очереди | несколько кадров сразу | |
| 2K, с потерями | 197 | 199 | 291 |
| 2K, без потерь | 144 | 148 | 185 |
| 4K, с потерями | 125 | 128 | 171 |
| 4K, без потерь | 56 | 57 | 66 |
Лучшее сочетание числа потоков и размера пачки в правом столбце: 32×1 на 2K с потерями и на обоих кадрах 4K, 16×2 на 2K без потерь.
Левый и средний столбцы расходятся на 1–3 %. Две разные программы на одном стенде, на одних файлах, в один день дают одинаковые результаты для библиотеки NVIDIA. Значит, программа измерений Fastvideo не мешает ей работать в полную силу, и весь прирост правого столбца — от 1,2 до 1,5 раза — даёт схема запуска, а не разница программ.
У декодирования есть поправка, без которой столбцы не сравнить. Счётчик штатного примера NVIDIA включает в себя разбор сжатого потока и вызов декодирования, но разбор он измеряет целыми секундами, поэтому в его результат время разбора оказывается нулевым. Счётчик программы Fastvideo считает ту же область целиком, вместе с разбором. Величину, которой они отличаются, измерили прямо: обеим программам дали кадры по очереди, в обоих случаях кадр остаётся на видеокарте, и взяли разность.
| Кадр и режим | Пример NVIDIA, мс на кадр | Программа Fastvideo, мс на кадр | Разность | Доля времени кадра |
|---|---|---|---|---|
| 2K, с потерями | 3,16 | 3,38 | 0,23 мс | 6,7 % |
| 2K, без потерь | 4,01 | 4,21 | 0,20 мс | 4,7 % |
| 4K, с потерями | 4,63 | 5,15 | 0,52 мс | 10,1 % |
| 4K, без потерь | 10,61 | 10,98 | 0,37 мс | 3,3 % |
То есть на декодировании левый столбец завышен на 3–10 % ещё до всякого сравнения, и разница между ним и средним столбцом объясняется целиком этим, а не скоростью библиотеки.
Декодирование, кадров в секунду
| Кадр и режим | Пример NVIDIA | Программа Fastvideo | Программа Fastvideo |
|---|---|---|---|
| кадры по очереди | кадры по очереди | несколько кадров сразу | |
| 2K, с потерями | 317 | 296 | 1575 |
| 2K, без потерь | 249 | 237 | 470 |
| 4K, с потерями | 217 | 194 | 581 |
| 4K, без потерь | 95 | 91 | 146 |
Лучшее сочетание в правом столбце: 8×2 на обоих строках для 2K, 32×1 на обоих строках для 4K.
Схема запуска Fastvideo ускоряет саму библиотеку nvJPEG2000: кодирование в 1,2–1,5 раза, декодирование в 1,6–5,3 раза. Считать надо от среднего столбца к правому: там одна и та же программа с одним и тем же счётчиком, и отличается только подача кадров. Числа взяты по лучшему сочетанию потоков и пачки, найденному перебором (раздел 4.3); по задачам прибавка разложена в таблице в начале статьи.
Энергия на кадр — столбца для примера NVIDIA здесь нет, и вот почему. Энергию считает счётчик самой видеокарты, той же разностью двух прогонов. Он считает всё время, пока программа работает, а штатный пример перечитывает исходные файлы с диска на каждую пачку, тогда как программа Fastvideo читает файл один раз. На кодировании 4K это 10,7 секунды чтения из двадцати четырёх секунд работы: в джоулях на кадр у примера NVIDIA были бы в основном данные от диска, а не от кодека. На скорость это не влияет — там у каждой программы взят её собственный счётчик, а он время чтения файлов не считает.
Энергия одного кадра, джоулей
| Кадр и режим | Кодирование | Декодирование | ||
|---|---|---|---|---|
| кадры по очереди | несколько кадров сразу | кадры по очереди | несколько кадров сразу | |
| 2K, с потерями | 0,68 | 0,59 | 0,45 | 0,22 |
| 2K, без потерь | 1,45 | 1,36 | 0,90 | 0,79 |
| 4K, с потерями | 1,38 | 1,22 | 0,87 | 0,62 |
| 4K, без потерь | 4,42 | 4,15 | 2,77 | 2,46 |
Обе колонки — программа измерений Fastvideo и библиотека nvJPEG2000, отличается только подача кадров. Схема запуска экономит от 7 % энергии кадра при кодировании без потерь и до двух раз при декодировании 2K с потерями: карта делает ту же работу за меньшее время и меньше простаивает в ожидании следующего кадра.
В сравнении двух кодеков в разделах 6 и 7 штатные программы NVIDIA не участвуют. Библиотека nvJPEG2000 запущена там схемой Fastvideo, то есть в лучшем для неё виде из измеренных. Сравнение со штатным примером сделало бы отрыв fvJPEG2000 больше, чем он есть.
Условия измерений. RTX 4090, драйвер 610.88, Windows 11, процессор с 32 логическими ядрами. Параметры сжатия — те же, что в разделе 3.1: кодоблок 32×32, шесть уровней разложения, один слой качества, прогрессия LRCP, без разбиения на тайлы. В клетках стоят результаты измерений, которые выполняет сама программа. Каждая точка измерена дважды — на одном числе кадров и на вдвое большем; в большинстве точек два измерения расходятся в пределах 1 %, в двух точках правого столбца — до 7 %. Все точки этих таблиц сняты при полной частоте видеокарты, от 2745 до 2760 МГц; частота записывалась десять раз в секунду во время каждого измерения.
Это отдельный прогон, а не тот, из которого сделаны разделы 6 и 7, поэтому с ними числа расходятся на несколько процентов: сетка сочетаний здесь короче. Сравнивать между собой нужно столбцы внутри этих таблиц — они сняты подряд, в одних и тех же условиях. Скрипт прогона и полные протоколы лежат в репозитории (раздел 14).