Comments 23
> В H.264/MPEG-4 AVC цифровой поток должен всегда начинаться с блока адресации IDR,
но это же не так. Intra refresh используется в низколатентном стриминге и на спутнике.
но это же не так. Intra refresh используется в низколатентном стриминге и на спутнике.
+1
Как видно из таблицы 1, применение инструкций Intel SSE и Intel AVX2 обеспечивает повышение производительности в 100 раз,
Даже если учесть, что работа идет с 8-битными целыми, максимальное теоретическое ускорение, которого можно добиться с SSE — 16 раз. Все остальное — разворачивание циклов, эффективный доступ к памяти, и прочее, напрямую не связанное с SIMD.
+3
Вы совершенно правы, спасибо! Обязательно передам это автору оригинальной статьи для правки.
+1
Вы не совсем правы. Есть такая операция в 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.
__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.
+3
И вам спасибо, и вы правы :) но там в таблице результат улучшения аж в 101 раз дан для SSE, причем, не для одной ф-ии, а как я понимаю, для целого блока кода. Так что без дополнительных оптимизаций типа доступа к памяти тут не обошлось.
0
Использование SIMD инструкций как бы подразумевает, что будут не только, например, выполняться сложение для всех элементов вектора с помощью одной операции, но также векторная загрузка исходных данных и векторное сохранение результатов. Опять же, так как, векторная загрузка/сохранение оптимально работает для выровненных данных, то хороший алгоритм должен по возможности использовать эту возможность.
0
На практике не удается достичь ускорения в 16 раз даже с выравниванием и разворачиванием.
Я не сомневаюсь, что что-то было ускорено в 100 раз, но говорить что это было сделано только за счет SSE или AVX2 — в корне не верно.
$ 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 — что может поставить компилятор в тупик (код с его точки зрения может выглядеть последовательным).
К стати, у вас в коде довольно нелогично — вы используете выровненные массивы данных, но при этом для чтения из них используете функцию _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
Занятно, что сжатие 4к h265 на 30 ядрах Xeon достигает лишь 7fps, в то время как топовые мобилки начала 2015 на Snapdragon 810 смогут это делать в реальном времени :-)
-1
вы не забывайте, что сжатие того же H264 может занимать разное время из-за разных выбранных оптимизаций.
Можно укладывать YUV в каждый макроблок и это тоже будет валидный H264. С H265 всё то же самое, просто спектр алгоритмов для сжатия ещё шире.
Так что мобилки на snapdragon печатают быстро, но такая фигня получается.
Можно укладывать YUV в каждый макроблок и это тоже будет валидный H264. С H265 всё то же самое, просто спектр алгоритмов для сжатия ещё шире.
Так что мобилки на snapdragon печатают быстро, но такая фигня получается.
+6
Очевидно, что в потоке, скажем, 4мбит/с простым YUV не отделаешься.
Про «фигню» давайте сравнение что-ли. Особенно в 4к.
60 ядерный комп за десятки тысяч $, который не может жать в fast режиме в реальном времени — вообще не выполняет свою функцию.
Про «фигню» давайте сравнение что-ли. Особенно в 4к.
60 ядерный комп за десятки тысяч $, который не может жать в fast режиме в реальном времени — вообще не выполняет свою функцию.
0
А у него функция обязательно в реальном времени кодировать?
Не забывайте, даже для h264 аппаратные решения дают существенно более плохое качество при равном битрейте, чем x264 с хорошим пресетом. Для h265, скорее всего, отрыв намного сильнее.
Мобилки, скорее всего, h265 используют только потому, что он умеет 4К, а не потому, что в 2 раза экономится битрейт при сравнимом качестве. Те алгоритмы, что дают эту экономию, аппаратно просто не реализованы.
Не забывайте, даже для h264 аппаратные решения дают существенно более плохое качество при равном битрейте, чем x264 с хорошим пресетом. Для h265, скорее всего, отрыв намного сильнее.
Мобилки, скорее всего, h265 используют только потому, что он умеет 4К, а не потому, что в 2 раза экономится битрейт при сравнимом качестве. Те алгоритмы, что дают эту экономию, аппаратно просто не реализованы.
0
Мне вдруг стало интересно, сколько процентов аккумулятора эти мобилки станут расходовать каждую минуту и за сколько минут разогреваться выше 50° по Цельсию во время видеозаписи.
0
UFO just landed and posted this here
QuickSync не забросили, и поддержка HEVC там есть. Но не бесплатно: software.intel.com/en-us/intel-media-server-studio/try-buy
0
UFO just landed and posted this here
Лучше бы открыли доступ к внутренностям QuickSync, чтобы задействовать эти аппаратные функции из x264/x265 с хорошим качеством на выходе, а не как сейчас — вот вам закрытый QuickSync без настроек, годный максимум для превью файлов или пережатия на мобильные устройства ( compression.ru/video/codec_comparison/h264_2012/ )
0
Sign up to leave a comment.
H.265/HEVC. Оптимизация под архитектуру Intel