Компания
190,96
рейтинг
12 ноября 2014 в 10:25

Разное → H.265/HEVC. Оптимизация под архитектуру Intel перевод


Текущую ситуацию в области медиакодеков, можно описать буквально несколькими словами: простые решения себя исчерпали. С каждым годом материал для кодирования становится все сложнее, а требования к качеству результата – все выше. В этих условиях, когда лобовая атака уже не дает эффекта, особое значение приобретает оптимизация как кодирования, так и воспроизведения медиа под конкретные платформы с использованием их самых современных возможностей. Чего можно добиться такой оптимизацией, мы покажем на примере перспективного кодека Н.265. В качестве целевой платформы рассмотрим серверное решение Intel — процессор Xeon.

Краткое описание H.265/HEVC

Стандарт H.265/HEVC (High-Efficiency Video Coding — высокоэффективное кодирование видео) — это самый последний стандарт видеокодека, разработанный совместно Международным союзом электросвязи ITU-T и ISO/IEC. Цель этого стандарта — повысить эффективность сжатия и снизить потери данных. H.265/HEVC, по сравнению с предыдущим стандартом H.264/AVC, обладает вдвое более высокой степенью сжатия при равном субъективном качестве изображения. Технология HEVC позволяет поставщикам видео передавать высококачественные видеоматериалы с меньшей нагрузкой на сеть.
Отметим основные функциональные новшества, примененные в Н.265:
  • Особые возможности для произвольного доступа и сращивания цифровых потоков. В H.264/MPEG-4 AVC цифровой поток должен всегда начинаться с блока адресации IDR, а в HEVC поддерживается произвольный доступ.
  • Изображение разделяется на единицы дерева кодирования (CTU), каждая из которых содержит блоки дерева кодирования (CTB) яркости и цветности. Во всех прежних стандартах кодирования видео использовался фиксированный размер массива для выборок яркости — 16×16. HEVC поддерживает блоки CTB разного размера, который выбирается в зависимости от потребностей кодировщика с точки зрения памяти и вычислительной мощности.
  • Каждый блок кодирования (СВ) может быть рекурсивно разделен на блоки преобразования (ТВ). Разделение определяется остаточным квадродеревом. В отличие от прежних стандартов в HEVC один блок ТВ может охватывать несколько блоков предсказания (РВ) для перекрестных предсказываемых единиц кодирования (CU).
  • Направленное предсказание с 33 различными направлениями ориентации для блоков преобразования (TB) размером от 4×4 до 32×32. Возможное направление предсказания — все 360 градусов. HEVC поддерживает различные методики кодирования предсказания интракадров.

H.265/HEVC налагает исключительно высокие требования по вычислительной мощности и на клиентские устройства, и на внутренние серверы транскодирования.

Проблемы производительности HEVC

Существующий проект HEVC Test Model (HM) реализует только основную функциональность стандарта; фактическая производительность по-прежнему далека от необходимой в реальной среде. Два основных недостатка этого проекта:
  • Отсутствие параллельной схемы.
  • Неэффективная настройка векторизации.



Рисунок 1. Профиль проекта HM — параллельная работа потоков


Рисунок 2. Профиль проекта HM — ресурсоемкий код

Этот кодек HEVC потребляет, по сравнению с H.264, в 100 раз больше ресурсов ЦП на стороне сервера и в 10 раз больше — на стороне клиента.
Кодек H.265/HEVC привлек внимание множества компаний и организаций во всем мире, что повлекло оптимизацию его производительности и фактическую разработку. Существует несколько проектов с открытым исходным кодом.
  • OpenHEVC (совместим с HM10.0, оптимизация декодера)
  • x265 (совместим с HM, распараллеливание и векторизация)

Для оценки производительности кодировщика x265 на платформе с процессорами Intel® Xeon® (E5-2680, 2,7 ГГц, 8*2 физических ядер, кодовое название — Sandy Bridge) мы запустили видео с разрешением 720p и частотой 24 кадра в секунду. Разработчики x265 проделали большую работу для оптимизации исходного стандарта с целью распараллеливания обработки задач и данных. Тем не менее, наш тест показал, что кодек может использовать лишь 6 ядер в системе с 32 логическими ядрами (с включенным SMT). Таким образом, кодек далеко не в полной мере использует ресурсы современных многоядерных платформ.


Рисунок 3. Нагрузка на ЦП в проекте X.265


Рисунок 4. Проект X.265 с настройкой Intel® SIMD

В проекте x265 также были использованы инструкции Intel® SIMD (автогенерация компилятором), что обеспечило повышение производительности более чем на 70%. Вместе с дальнейшей оптимизацией компиляторными опциями, компилятор Intel обеспечивает удвоение производительности на платформе IA. Тем не менее, производительность кодировщика по-прежнему существенно ниже, чем требуется для кодировщика реального времени, особенно для видео высокой четкости с разрешением 1080p.
Ниже мы покажем результаты, достигнутые китайской компанией Strongene при поддержке специалистов компании Intel на пути оптимизации созданного ей кодека H.265/HEVC под различные платформы Intel.

Оптимизация HEVC под платформу Intel® Xeon™

Основную часть самых ресурсоемких функций по обработке видео и изображений составляют интенсивные вычисления блочных данных. Для их оптимизации можно использовать инструкции векторизации Intel® SIMD. В кодировщике в составе кодека Strongene, согласно данным профилирования, с помощью инструкций Intel SSE можно провести ручную векторизацию всех наиболее ресурсоемких функций, таких как кадровая интерполяция низкой сложности с компенсацией движения; целочисленное преобразование без транспозиции; преобразование Адамара; вычисление сумм абсолютных разностей (SAD)/квадратов разности (SSD) с наименьшим избыточным использованием памяти. Мы включили инструкции Intel SSE в виде интринсик-функций, как показано на рис. 5.


Рисунок 5. Пример включения инструкций Intel® SIMD/SSE в кодеке Stongene

Разработчики Strongene переписали все ресурсоемкие функции, чтобы добиться наибольшего прироста производительности кодировщика. На рис. 6 показаны наши данные профилирования в сценарии кодирования видео стандарта 1080p с помощью HEVC. Видно, что 60% ресурсоемких функций обрабатываются инструкциями Intel SIMD.


Рисунок 6. Результаты профилирования функций кодирования Strogene

Инструкции Intel AVX2 с вычислением 256-разрядных целочисленных значений обладают вдвое более высокой производительностью по сравнению с прежним кодом Intel SSE, работающим со 128-разрядными значениями. Набор инструкций Intel AVX2 поддерживается платформой
Intel Xeon (Haswell), выпуск которой начат в 2014 году. Для оценки производительности встроенных функций Intel AVX2 мы используем распространенное вычисление сумм абсолютных разностей для блока 64*64.

Таблица 1. Результаты реализации Intel® SSE и Intel® AVX2
Циклы ЦП Исходный код Intel® SSE Intel® AVX2
Запуск 1 98877 977 679
Запуск 2 98463 1092 690
Запуск 3 98152 978 679
Запуск 4 98003 943 679
Запуск 5 98118 954 678
Среднее 98322,6 988,8 681
Ускорение 1,00 99,44 144,38

Как видно из таблицы 1, применение инструкций Intel SSE и Intel AVX2 обеспечивает повышение производительности в 100 раз, при этом код Intel AVX2 дополнительно выигрывает еще 40% по сравнению с Intel SSE.
Как мы видели ранее, в большинстве существующих реализаций используются не все ядра многоядерных платформ. Опираясь на последнюю многоядерную архитектуру Intel Xeon с параллельной зависимостью между алгоритмами на основе CTB, разработчики Strongene предлагают заменить исходные методы OWF и WPP параллельной структурой IFW, а затем разработать трехуровневую схему управления потоками, чтобы гарантировать полное использование структурой IFW всех ядер ЦП для ускорения кодирования HEVC.


Рисунок 7. Параллельная работа потоков и использование ЦП в кодировщике Strongene

За счет применения новой параллельной структуры WHP и полной реализации инструкций Intel SIMD соответственно на уровне задач и уровне данных разработчикам кодировщика Strongene удалось добиться весьма значительного повышения производительности на процессорах x86 для видео с разрешением 1080p, используя вычислительные ресурсы всех ядер, как показано на рис. 8.

Дальнейшая настройка с использованием SMT/HT

Также представляет интерес зависимость производительности кодека от включения в системе широко распространенной на всех платформах с архитектурой Intel одновременной многопоточности (SMT), также называемой технологией гипертрединга (HT).

Таблица 2. Скорость кодирования Strongene HEVC на платформе Intel® Xeon®

Как видно из таблицы (показано желтым цветом) на платформе Ivy Bridge (процессор Intel Xeon E5-2697 v2 для отключенного SMT кодирование видео HEVC с разрешением 1080p осуществляется в реальном времени!

Добившись огромнейшего увеличения производительности, мы продолжили изучение возможностей кодирования Strongene HEVC на платформе Ivy Bridge, уделяя внимание скорости потока и вопросам качества.

Таблица 3. Сравнение производительности кодеков H.264 и H.265

В таблице 3 видно, что кодек H.265/HEVC снижает объем данных на 50% при сохранении прежнего качества видеоизображения.

H.265/HEVC, по всей видимости, станет наиболее популярным стандартом видео в ближайшее десятилетие. Во множестве приложений и продуктов мультимедиа в настоящее время реализуется поддержка HEVC. В этом документе мы реализовали основанное на ЦП полнофункциональное решение HEVC реального времени на платформах Intel с новыми технологиями IA. Наше оптимизированное решение на базе процессоров Intel развернуто в компании Xunlei, занимающейся предоставлением услуг видео через Интернет, и будет способствовать повсеместному внедрению и распространению технологии H.265/HEVC.
Автор: @saul Yang Lu
Intel
рейтинг 190,96

Комментарии (23)

  • +1
    > В H.264/MPEG-4 AVC цифровой поток должен всегда начинаться с блока адресации IDR,

    но это же не так. Intra refresh используется в низколатентном стриминге и на спутнике.

  • +3
    Как видно из таблицы 1, применение инструкций Intel SSE и Intel AVX2 обеспечивает повышение производительности в 100 раз,

    Даже если учесть, что работа идет с 8-битными целыми, максимальное теоретическое ускорение, которого можно добиться с SSE — 16 раз. Все остальное — разворачивание циклов, эффективный доступ к памяти, и прочее, напрямую не связанное с SIMD.
    • +1
      Вы совершенно правы, спасибо! Обязательно передам это автору оригинальной статьи для правки.
    • +3
      Вы не совсем правы. Есть такая операция в SSE2 — _mm_sad_epu8 (в AVX2 — _mm256_sad_epu8), которая позволяет за одну операцию найти сумму абсолютных разностей 1-байтных беззнаковых целых чисел (упоминается в статье). Иначе говоря:

      __m128i a, b, c;
      с = _mm_sad_epu8(a, b); эквивалетна:

      unsigned long long c[2];
      unsigned char a[16], b[16];

      c[0] =
      a[0] > b[0]? a[0] — b[0]: b[0] — a[0] +
      a[1] > b[1]? a[1] — b[1]: b[1] — a[1] +
      a[2] > b[2]? a[2] — b[2]: b[2] — a[2] +
      a[3] > b[3]? a[3] — b[3]: b[3] — a[3] +
      a[4] > b[4]? a[4] — b[4]: b[4] — a[4] +
      a[5] > b[5]? a[5] — b[5]: b[5] — a[5] +
      a[6] > b[6]? a[6] — b[6]: b[6] — a[6] +
      a[7] > b[7]? a[7] — b[7]: b[7] — a[7];

      c[1] =
      a[8] > b[8]? a[8] — b[8]: b[8] — a[8] +
      a[9] > b[9]? a[9] — b[9]: b[9] — a[9] +
      a[10] > b[10]? a[10] — b[10]: b[10] — a[10] +
      a[11] > b[11]? a[11] — b[11]: b[11] — a[11] +
      a[12] > b[12]? a[12] — b[12]: b[12] — a[12] +
      a[13] > b[13]? a[13] — b[13]: b[13] — a[13] +
      a[14] > b[14]? a[14] — b[14]: b[14] — a[14] +
      a[15] > b[15]? a[15] — b[15]: b[15] — a[15];

      Т.е. одна эта операция SSE2 позволяет заменить 62 операции для скалярного кода (для AVX2 — 124 операции соответственно). Отсюда и возможен потенциальный выигрыш до ста раз. Хотя конечно на большинстве других операций он гораздо скромнее — порядка 10 раз для SSE2 кода и 15 раз для AVX2 кода.
      Данные взяты из проекта Simd.
      • 0
        И вам спасибо, и вы правы :) но там в таблице результат улучшения аж в 101 раз дан для SSE, причем, не для одной ф-ии, а как я понимаю, для целого блока кода. Так что без дополнительных оптимизаций типа доступа к памяти тут не обошлось.
        • 0
          Использование SIMD инструкций как бы подразумевает, что будут не только, например, выполняться сложение для всех элементов вектора с помощью одной операции, но также векторная загрузка исходных данных и векторное сохранение результатов. Опять же, так как, векторная загрузка/сохранение оптимально работает для выровненных данных, то хороший алгоритм должен по возможности использовать эту возможность.
      • 0
        На практике не удается достичь ускорения в 16 раз даже с выравниванием и разворачиванием.

        $ cat /proc/cpuinfo | grep CPU
        model name	: Intel(R) Core(TM) i5-4258U CPU @ 2.40GHz
        
        $ cc -O2 -lm ./sad.c -o sad -msse4  && time ./sad
        30592637 scalar in 3.350590 sec
        30592637 SSE4 in 0.281125 sec
        30592637 optim SSE4 in 0.270903 sec
        

        Полный код примера
        #include <time.h>
        #include <stdint.h>
        #include <malloc.h>
        #include <smmintrin.h>
        
        
        int sad(uint8_t *buf1, uint8_t *buf2, int buf_length)
        {
          int i;
          int sum = 0;
          for (i = 0; i < buf_length; i++) {
            sum += abs(buf1[i] - buf2[i]);
          }
          return sum;
        }
        
        
        int sad_sse(uint8_t *buf1, uint8_t *buf2, int buf_length)
        {
          int i;
          __m128i sum = _mm_setzero_si128();
          int res[4];
          for (i = 0; i < buf_length; i += 16) {
            __m128i b1 = _mm_loadu_si128((__m128i *) &buf1[i]);
            __m128i b2 = _mm_loadu_si128((__m128i *) &buf2[i]);
            __m128i s1 = _mm_sad_epu8(b1, b2);
            sum = _mm_add_epi32(sum, s1);
          }
          sum = _mm_hadd_epi32(sum, sum);
          sum = _mm_hadd_epi32(sum, sum);
          return _mm_cvtsi128_si32(sum);
        }
        
        
        int sad_sse_opt(uint8_t *buf1, uint8_t *buf2, int buf_length)
        {
          int i;
          __m128i sum = _mm_setzero_si128();
          int res[4];
          for (i = 0; i < buf_length; i += 64) {
            __m128i b1 = _mm_loadu_si128((__m128i *) &buf1[i]);
            __m128i b2 = _mm_loadu_si128((__m128i *) &buf2[i]);
            __m128i b3 = _mm_loadu_si128((__m128i *) &buf1[i + 16]);
            __m128i b4 = _mm_loadu_si128((__m128i *) &buf2[i + 16]);
            __m128i b5 = _mm_loadu_si128((__m128i *) &buf1[i + 32]);
            __m128i b6 = _mm_loadu_si128((__m128i *) &buf2[i + 32]);
            __m128i b7 = _mm_loadu_si128((__m128i *) &buf1[i + 48]);
            __m128i b8 = _mm_loadu_si128((__m128i *) &buf2[i + 48]);
            __m128i s1 = _mm_sad_epu8(b1, b2);
            __m128i s2 = _mm_sad_epu8(b3, b4);
            __m128i s3 = _mm_sad_epu8(b5, b6);
            __m128i s4 = _mm_sad_epu8(b7, b8);
            sum = _mm_add_epi32(sum, s1);
            sum = _mm_add_epi32(sum, s2);
            sum = _mm_add_epi32(sum, s3);
            sum = _mm_add_epi32(sum, s4);
          }
          sum = _mm_hadd_epi32(sum, sum);
          sum = _mm_hadd_epi32(sum, sum);
          return _mm_cvtsi128_si32(sum);
        }
        
        
        void *malloc16 (size_t s) {
          unsigned char *p;
          unsigned char *porig = malloc (s + 0x10);
          p = (void *)(((int) porig + 16) & (~0xf));
          *(p-1) = p - porig;
          return p;
        }
        
        void free16(void *p) {
          unsigned char *porig = p;
          porig = porig - *(porig-1);
          free(porig);
        }
        
        
        int main(int argc, char **argv)
        {
          float   time;
          clock_t start;
          int     i;
          uint8_t*buf1, *buf2;
          int     buf_length = 64 * 1024 * 1024;
          int     res;
          int     times = 50;
        
          buf1 = malloc16(buf_length * sizeof(uint8_t));
          buf2 = malloc16(buf_length * sizeof(uint8_t));
        
          for (i = 0; i < buf_length; i += 333) {
            buf1[i] = i % 256;
          }
          for (i = 0; i < buf_length; i += 1741) {
            buf1[i] = i % 256;
          }
        
          start = clock();
          for (i = 0; i < times; ++i) {
            res = sad(buf1, buf2, buf_length);
          }
          printf("%d scalar in %f sec\n", res, (float)(clock() - start) / CLOCKS_PER_SEC);
        
          start = clock();
          for (i = 0; i < times; ++i) {
            res = sad_sse(buf1, buf2, buf_length);
          }
          printf("%d SSE4 in %f sec\n", res, (float)(clock() - start) / CLOCKS_PER_SEC);
        
          start = clock();
          for (i = 0; i < times; ++i) {
            res = sad_sse_opt(buf1, buf2, buf_length);
          }
          printf("%d optim SSE4 in %f sec\n", res, (float)(clock() - start) / CLOCKS_PER_SEC);
        
          free16(buf1);
          free16(buf2);
        
          return 0;
        }
        

        Я не сомневаюсь, что что-то было ускорено в 100 раз, но говорить что это было сделано только за счет SSE или AVX2 — в корне не верно.
        • 0
          По моим данным выигрыш от для данного примера на SSE2 получается порядка 22-25 раз.
          К стати, у вас в коде довольно нелогично — вы используете выровненные массивы данных, но при этом для чтения из них используете функцию _mm_loadu_si128, которая вдвое медленнее чем _mm_load_si128. На одном этом теряете порядка 20% производительности.
          С разворачиванием цикла (если опустить вопрос о ее целесообразности для кода с SIMD инструкциями — ибо процессор может одновременно исполнить не более двух векторных инструкций за такт) тоже не все гладко — если операции _mm_loadu_si128 и _mm_sad_epu8 у вас потенциально могут исполняться одновременно, то функции _mm_add_epi32 (к стати почему не _mm_add_epi64 ?) работают с oдной переменной sum — что может поставить компилятор в тупик (код с его точки зрения может выглядеть последовательным).
          • 0
            По моим данным выигрыш от для данного примера на SSE2 получается порядка 22-25 раз.
            Я не понял, что вы имеете в виду, что у вас за данные. Вы запустили этот же код у себя и получили выигрыш 22-25 раз?

            вы используете выровненные массивы данных, но при этом для чтения из них используете функцию _mm_loadu_si128
            С этими функциями вообще смешная история. _mm_load_si128 с выравниванием конечно работает быстрее, чем _mm_loadu_si128 без выравнивания. Но еще быстрее работает именно _mm_loadu_si128 c выравниванием. Но здесь это не заметно, потому что на самом деле 64 мб * 50 раз * 2 массива / 0.270903 сек. уже равно скорости чтения 23,6 GB/s, что очень близко к максимальной пропускной способности памяти 25,6 GB/s.
            • 0
              Я использовал тесты производительности встроенные в библиотеку Simd. Хотя вы правы: в качестве тестов там используется серая картинка в разрешении Full HD размеров 2 MB. Так как все данные влазят в процессорный кеш, то получается выигрыш в 20-25 раз. Если размер тестовой картинки увеличить, то выигрыш от оптимизации будет всего 7.5 раз и для SSE2 и для AVX2 кода — все упрется в шину данных. Но честно говоря в моем случае (видеоаналитика), данных обычно не так много — часто все влезает в кеш.
              • 0
                А вы делаете аналитику на процессоре или на Cuda/Quicksync?
                • 0
                  На процессоре.
                  • 0
                    рассматривали другие варианты?
                    • +1
                      Конечно. Однако пока лучшую производительность показывает CPU.
  • –1
    Занятно, что сжатие 4к h265 на 30 ядрах Xeon достигает лишь 7fps, в то время как топовые мобилки начала 2015 на Snapdragon 810 смогут это делать в реальном времени :-)
    • +6
      вы не забывайте, что сжатие того же H264 может занимать разное время из-за разных выбранных оптимизаций.

      Можно укладывать YUV в каждый макроблок и это тоже будет валидный H264. С H265 всё то же самое, просто спектр алгоритмов для сжатия ещё шире.

      Так что мобилки на snapdragon печатают быстро, но такая фигня получается.
      • 0
        Очевидно, что в потоке, скажем, 4мбит/с простым YUV не отделаешься.
        Про «фигню» давайте сравнение что-ли. Особенно в 4к.
        60 ядерный комп за десятки тысяч $, который не может жать в fast режиме в реальном времени — вообще не выполняет свою функцию.
        • 0
          А у него функция обязательно в реальном времени кодировать?

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

          Мобилки, скорее всего, h265 используют только потому, что он умеет 4К, а не потому, что в 2 раза экономится битрейт при сравнимом качестве. Те алгоритмы, что дают эту экономию, аппаратно просто не реализованы.
    • 0
      Мне вдруг стало интересно, сколько процентов аккумулятора эти мобилки станут расходовать каждую минуту и за сколько минут разогреваться выше 50° по Цельсию во время видеозаписи.
  • 0
    vp9 пробовали?
    КвикСинк уже забросили?
    • 0
      QuickSync не забросили, и поддержка HEVC там есть. Но не бесплатно: software.intel.com/en-us/intel-media-server-studio/try-buy
    • 0
      Там уже скоро vp10 будет, гугл штампует кодеки c бешенной скоростью, а толку ноль.
  • 0
    Лучше бы открыли доступ к внутренностям QuickSync, чтобы задействовать эти аппаратные функции из x264/x265 с хорошим качеством на выходе, а не как сейчас — вот вам закрытый QuickSync без настроек, годный максимум для превью файлов или пережатия на мобильные устройства ( compression.ru/video/codec_comparison/h264_2012/ )

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разное