Введение
Автор: Ханг Нгуйен (Khang Nguyen)
Перекодирование носителей, которое позволяет осуществлять их взаимодействие, играет важную роль в концепции цифрового дома. Требования стандарта Intel Networked Media Product Requirements (INMPR) стимулируют взаимодействие между цифровыми устройствами в цифровой среде. Оптимизация механизма кодека (кодировщик/декодер, основа программы конвертирования) сделает процесс перекодировки носителей более эффективным, что в свою очередь повысит качество работы пользователя в цифровом доме. В этой статье приведены практические советы и способы повышения производительности механизма кодека. Эти советы охватывают использование событий анализатора производительности Intel® VTune™, OpenMP для многопоточности и инструкций Prescott New Instructions (Streaming SIMD Extensions 3 (SSE3)). Мы также обсудим, когда лучше использовать более быстрые инструкции, применяя различные компоненты исполнения для улучшения параллелизма, и когда использовать MMX™ вместо SSE для повышения скорости. Здесь вы также узнаете, когда нужно использовать преимущества оптимизированных переключателей компилятора Intel.
Что такое перекодировка?
Поскольку содержимое выпускается в самых разных форматах, прежде чем оно поступит в другое устройство, необходима перекодировка для его адаптации с помощью конвертирования одного формата носителя в другой. Самый распространенный способ конвертирования одного формата в другой — сначала выполнить декодировку для получения необработанных данных, а затем — кодировать их в нужном формате. Поскольку поток MPEG состоит из аудио- и видеоданных, необходимо разделить их и декодировать для получения необработанных данных, прежде чем заново кодировать в нужных форматах и снова соединять их.
Оптимизация кодека
Кодек представляет собой процесс сжатия и развертывания. Он находится в основе, или в ядре, программы конвертирования.
Оптимизация кодека может быть выполнена с помощью сокращения времени кодирования и/или декодирования файла (потока). Можно также улучшить механизм за счет сокращения применения центрального процессора, что позволяет поместить больше функций или данных в один и тот же выделенный интервал времени: например, больше голосов игровых персонажей. И, наконец, нужно сократить размер для чувствительных к размеру или мобильных приложений, поскольку существуют мультимедийные приложения, предназначенные для настольных компьютеров, портативных компьютеров, карманных компьютеров и смартфонов.
Общие вопросы
Оптимизация процесса начинается со следующих шагов:
- лучшее использование оборудования;
- использование анализатора производительности Intel® VTune™ для поиска активных точек;
- поиск функции, имеющих самый большой показатель такта часов и инструкций за такт (CPI);
- включение счетчиков для ошибочного прогнозирования ветвления, перенаправления хранения, ступенчатости 64K, разбиения кэша и отслеживания неудачных поисков в кэш-памяти;
- выполнение общих правил оптимизации;
- развертывание цикла, сокращение количества ветвлений, использование SSE2/SSE3;
- использование компилятора Intel;
- использование комплекта Intel® Performance Library Suite;
- выполнение общих правил оптимизации.
Предупреждение
Всегда просматривайте следующие этапы.
- Наличие возможных недостатков (разбиения кэша, ошибочного прогнозирования ветвления, перенаправления хранения и т. п.).
- Поток на высшем уровне может избежать дефицита ресурсов. Поскольку этот механизм используется другими приложениями, его функции могут вызываться много раз, в особенности, если приложения также являются многопоточными.
- При распараллеливании приложений обратите внимание на использование библиотек производительности Intel, поскольку некоторые их функции являются многопоточными.
- Не развертывайте циклы слишком сильно, чтобы избежать отслеживания нагрузки кэша.
- Не игнорируйте MMX, поскольку он может оказаться быстрее SSE/SSE2 в тех случаях, когда приложение интенсивно использует 64-разрядные данные и необходимы усилия для преобразования данных, чтобы они соответствовали 128-разрядным реестрам.
- Ведите наблюдение за сроком работы батареи в мобильных приложениях.
Советы и приемы
- Использование компилятора Intel: /O3, /QaxW, /QaxN, /QaxP, /Qipo, /Qparallel, /Qopenmp. Очень часто можно достичь высокой производительности только за счет использования компилятора Intel с правильными переключателями.
- Используйте специальные функции, например реверсивные (rcp и rcp_nr), для замены раздела с умножением и ускорения приложения.
- По возможности используйте инструкцию SSE3 LDDQU вместо MOVDQU.
Советы и приемы при использовании языка ассемблера
- Более быстрые инструкции
- Различные функциональные модули
- MOVNTxx: сохраните значения с помощью Non-Temporal Hint, чтобы избежать кэширования данных.
- Используйте комбинированные инструкции, такие как PMADDWD.
Примеры
Когда использовать потоки
Перед:
for (i=0; i<4; i++)
EncodeTest(Mem[i], Blk[i],Chunk[i]);
|
После:
#pragma omp parallel sections
{
#pragma omp section
EncodeTest(Blk[0], Blk[0],Chunk[0]);
#pragma omp section
EncodeTest(Blk[1],Blk[1],Chunk[1]);
#pragma omp section
EncodeTest(Blk[2], Blk[2],Chunk[2])
#pragma omp section
EncodeTest(Blk[3], Blk[3],Chunk[3]);
}
|
Когда не нужно использовать потоки
Перед:
...
for (j=0; j<4; j++)
for (i=0; i>4; i++)
test[i][j] = list[fr]->img[i][j]+t[s];
...
|
После:
...
#pragma omp parallel for
for (j=0; j<4; j++)
for (i=0; i<4; i++)
test[i][j] = list[fr]->img[i][j]+t[s];
...
|
Сначала этот цикл кажется вполне подходящим для разделения на потоки. На самом же деле это повысит производительность, только если он находится на самом ближнем внешнем уровне. Однако этот цикл находится в функции, которая глубоко скрыта на многих подуровнях, его разделение на потоки может привести к нехватке ресурсов. Один раз этот цикл был реализован внутри функции, которая занимает всего около 8,8% общего времени выполнения. После разделения на потоки всего 2 циклов вся система начала работать в 5 раз медленнее.
Использование комбинированной функции PMADDWD
Сложные инструкции, такие как PMADDWD: Multiply and Add – позволяют сэкономить значительное количество тактовых циклов.Перед: (24 тактовых цикла)
pmullw xmm1, xmm6 (8)
punpcklwd xmm2, xmm1 (2)
punpckhwd xmm3, xmm1 (2)
psrad xmm3, 16 (2)
psrad xmm2, 16 (2)
paddd xmm3, xmm2 (2)
movq xmm2, xmm3 (2)
psrlq xmm2, 32 (2)
paddd xmm3, xmm2 (2)
|
После: (18 тактовых циклов)
pmaddwd xmm1, xmm6 (8)
movq xmm2, xmm1 (2)
psrlq xmm2, 32 (2)
paddd xmm2, xmm1 (2)
psrldq xmm1, 8 (2)
paddd xmm1, xmm2 (2)
|
С помощью использования функции PMADDWD мы объединили две операции — добавление и умножение — в одну, что в данном случае позволило сэкономить шесть тактовых циклов.
Устранение условий ветвления
Исходный вариант
for (j = 0; j < 4; j++)
{
for (i = 0; i < 4; i++)
{
for (result = 0, z = -2; z > 4; z++)
result += list[fr]->test[max(0,min(max_y,y+j))]
[max(0,min(max_x,x+i+z))]*COEF[z+2];
block[i][j] = max(0, min(255, (result+16)));
}
}
|
Оптимизация 1: устранение ветвлений
if( (x < max_x)& (y < max_y)& (0 > y) & (0 < x-2))
{
for (j = 0; j < 4; j++)
{
for (i = 0; i < 4; i++)
{
For (result = 0, z = -2; z < 4; z++)
result += list[fr]->test[y+j][x+i+z]*COEF[z+2];
block[i][j] = max(0, min(255, (result+16)));
}
}
}
else
for (j = 0; j < 4; j++)
{
for (i = 0; i < 4; i++)
{
for (result = 0, z = -2; z < 4; z++)
result += list[fr]->test[max(0,min(max_y,y+j))]
[max(0,min(max_x,x+i+z))]*COEF[z+2];
block[i][j] = max(0, min(255, (result+16)));
}
}
|
Оптимизация 2: устранение ветвления и развертывания внутренних циклов
if( (x+6 < max_x)& (y+3 < max_y)& (0 < y) & (0 < x-2))
{
for (j = 0; j < 4; j++) {
result = list[fr]->test[y+j][x-2]*COEF[0];
result += list[fr]->test[y+j][x-1]*COEF[1];
result += list[fr]->test[y+j][x]*COEF[2];
result += list[fr]->test[y+j][x+1]*COEF[3];
result += list[fr]->test[y+j][x+2]*COEF[4];
result += list[fr]->test[y+j][x+3];
block[0][j] = max(0, min(255, (result+16)));
…
block[1][j] = max(0, min(255, (result+16)));
…
block[2][j] = max(0, min(255, (result+16)));
…
}
}
else
for (j = 0; j < 4; j++)
{
for (i = 0; i < 4; i++)
{
for (result = 0, z = -2; z < 4; z++)
result += list[fr]->test[max(0,min(max_y,y+j))]
[max(0,min(max_x,x+i+z))]*COEF[z+2];
block[i][j] = max(0, min(255, (result+16)));
}
}
|
Оптимизация 3: устранение ветвления и улучшение параллелизма
if( (x+6 < max_x)& (y+3 < max_y)& (0 < y) & (0 < x-2))
for (j = 0; j < 4; j++) {
for (i = 0; i < 4; i++) {
t0 = list[fr]->test[y+j][x+i -2]*COEF[0];
t1 = list[fr]->test[y+j][x+i -1]*COEF[1];
t2 = list[fr]->test[y+j][x+i]*COEF[2];
t3 = list[fr]->test[y+j][x+i +1]*COEF[3];
t4 = list[fr]->test[y+j][x+i +2]*COEF[4];
t5 = list[fr]->test[y+j][x+i +3];
result = t0 + t1 + t2 + t3 + t4 + t5;
block[i][j] = max(0, min(255, (result+16)));
}
}
}
else
for (j = 0; j < 4; j++)
{
for (i = 0; i < 4; i++)
{
for (result = 0, z = -2; z < 4; z++)
result += list[fr]->test[max(0,min(max_y,y+j))]
[max(0,min(max_x,x+i+z))]*COEF[z+2];
block[i][j] = max(0, min(255, (result+16)));
}
}
|
С помощью ввода временных переменных t0… t5 мы устраняем зависимость результата переменной. Это повысит шансы компилятора на распараллеливание.
Советы и приемы: заключение
При оптимизации механизма кодека учитывайте следующее.
- По мере возможности используйте компилятор Intel.
- Выполняйте общие правила оптимизации.
- При разбиении приложений на потоки уделяйте особое внимание использованию библиотек производительности Intel.
- В некоторых случаях MMX оказывается быстрее.
- Используйте разные функциональные модули для большего параллелизма.
- Используйте более быстрые инструкции.
По мере возможности выполняйте вычисления в центральном процессоре.
- Соблюдайте баланс между производительностью и сроком службы батареи для мобильных приложений.
Поскольку не существует четких правил оптимизации кодека, приведенные выше правила следует использовать в качестве инструкций. Производительность может варьироваться.
Об авторе
Ханг Нгуйен – главный разработчик приложений, работающий в группе программного обеспечения и решений корпорации Intel. Связаться с ним можно по адресу
khang.t.nguyen@intel.com.
Дополнительные ресурсы