Оценка движения с помощью набора команд Intel® Streaming SIMD Extensions 4 (Intel® SSE4)

Введение
Оценка движения с помощью команд MPSADBW и PHMINPOSUW
Результаты
Заключение
A. SSE2 - Оптимизированная функция для блоков 4x4
B. Intel® SSE4 - Оптимизированная функция для блоков 4x4
C. SSE2 - Оптимизированная функция для блоков 8x8
D. Intel® SSE4 - Оптимизированная функция для блоков 8x8
E. SSE2 - Оптимизированная функция для блоков 16x16
F. Intel® SSE4 - Оптимизированная функция для блоков 8x8

Введение

Intel® SSE4 – это новый набор команд Single Instruction Multiple Data (SIMD), впервые реализованный в семействе процессоров Intel® Core™2 на базе 45-нанометровой технологии следующего поколения (Penryn) и предназначенный для улучшения производительности различных приложений, включая кодирование видео, обработку изображений и 3D игры. Intel SSE4 разработан на базе набора команд Intel® 64 и IA-32, которые являются наиболее популярными и широко используемыми вычислительными архитектурами для разработки 32- и 64-разрядных приложений. Intel SSE4 реализован в семействе процессоров Intel® Core™2 на базе 45-нанометровой технологии (Penryn).

В данном документе описано, как с помощью команд Intel SSE4 в программах кодирования видео можно повысить уровень производительности поиска целочисленных векторов движения (часто используемая функция оценки движения) от 1,6 до 3,8 раз. Для представления некоторых разбросов, которые используются при оценке движения и для иллюстрации того, как код может быть адаптирован к этим разбросам, применяются блоки трех разных размеров: 4x4, 8x8 и 16x16.

Оценка движения с помощью команд MPSADBW и PHMINPOSUW

Оценка движения является одним из основных «узких мест» кодировщиков видео. Эта задача включает в себя поиск наиболее соответствующих систем отсчета и часто использует около 40% от циклов процессора, необходимых для кодировщика. Качество поиска является фактором, который определяет коэффициент сжатия и качество кодированного видеоматериала. Часто для этой операции поиска создаются алгоритмические оптимизации и оптимизации SIMD, позволяющие повысить скорость кодировки. Неоптимизированная версия функции блочного сравнения для блоков размером 4x4 приведена в листинге 1. Код, представленный в качестве примера в данной статье, выполняет только поиск целочисленных векторов движения на этапе оценки движения.

Листинг 1. Неоптимизированная версия функции целочисленного блочного сравнения

 
int blockMatch4x4(const unsigned char* refFrame, int stepBytesRF /*stride*/, const 
unsigned char* curBlock, int stepBytesCB /*stride*/, int* matchBlock /*results*/, int 
frameWidth, int frameHeight)
{
   int lowSum = INT_MAX;
   int temSum = 0;
   int blockHeight = 4;
   int blockWidth = 4;
   const unsigned char *pRef, *pCur;
   for (int i=0; i<=frameHeight-blockHeight; i++)
   {
      for (int j=0; j<=frameWidth-blockWidth; j++)  
      {
         temSum = 0;
         pCur = curBlock;
         pRef = refFrame+i*stepBytesRF+j;
         for (int k=0; k<blockHeight; k++)
         {
            for (int l=0; l<blockWidth; l++)
            {
               temSum += labs(*pRef-*pCur);
               pCur++;
               pRef++;
            }
            pCur+=stepBytesCB-blockWidth;
            pRef+=stepBytesRF-blockWidth;
         }
         if (temSum < lowSum)
         {
            lowSum = temSum;
            *matchBlock = j;
            *(matchBlock+1) = i;
         }
      }
   }
   return 0;
}

Команды SIMD являются прекрасным средством для оценки движения, поскольку арифметические операции, выполняющиеся в процессе поиска, осуществляются для блоков пикселей и по определению параллельны. Команда PSADBW решения SSE2 широко используется разработчиками для оптимизации этой операции. PSADBW вычисляет две суммы абсолютной разности из пары 16-разрядных беззнаковых целых чисел. Одна сумма получается из беззнаковых целых чисел восьми младших байтов, а другая – сумма беззнаковых целых чисел восьми старших байтов [1].

В листинге 2 приведена функция оценки движения, оптимизированная для использования команды PSADBW. Эта функция находит совпадающие блоки для четырех блоков 4x4 в каждом вызове. PSADBW вычисляет сумму абсолютной разности беззнаковых целых чисел 8 байтов, однако ширина блока 4x4 составляет только 4 байта. Чтобы использовать эту команду, два строки вначале были извлечены для соединения двух 4-разрядных наборов данных в 8-разрядные. Кроме того, поскольку в каждой загрузке содержатся 16 последовательных байтов, данные загружаются из 4 последовательных блоков в течение одной операции. Следовательно, необходимо, чтобы данная функция находила совпадающий блок для четырех блоков при каждом вызове.

Листинг 2. SSE2 – Оптимизированная функция целочисленного блочного сравнения, осуществляющая поиск совпадающих блоков для четырех блоков 4x4 при каждом вызове.

 
//finds matching blocks for four 4x4 blocks in each call
 
int blockMatch4x4SSE2(const unsigned char* refFrame, int stepBytesRF, const unsigned 
 
char* curBlock, int stepBytesCB, int* matchBlock, int frameWidth, int frameHeight)
 
{
 
       unsigned int lowSum[4] = {UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX};
 
       unsigned int temSum = 0;
 
       int blockHeight = 4;
 
       int blockWidth = 4;
 
       const unsigned char *pRef, *pCur;
 
       __m128i s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
 
 
 
       pCur = curBlock;
 
       s0 = _mm_loadu_si128((__m128i*)pCur);
 
       s1 = _mm_loadu_si128((__m128i*)(pCur+stepBytesCB));
 
       s2 = _mm_loadu_si128((__m128i*)(pCur+2*stepBytesCB));
 
       s3 = _mm_loadu_si128((__m128i*)(pCur+3*stepBytesCB));
 
 
 
       s8 = _mm_unpacklo_epi32(s0, s1);
 
       s9 = _mm_unpacklo_epi32(s2, s3);
 
       s10 = _mm_unpackhi_epi32(s0, s1);
 
       s11 = _mm_unpackhi_epi32(s2, s3);
 
 
 
       for (int i=0; i<=frameHeight-blockHeight; i++)
 
       {
 
              for (int j=0; j<=frameWidth-blockWidth; j++)
 
              {
 
                    pRef = refFrame+i*stepBytesRF+j;
 
 
 
                    s6 = _mm_unpacklo_epi32(
 
                            _mm_cvtsi32_si128(*(unsigned int*)pRef), 
                            _mm_cvtsi32_si128(*(unsigned int*)(pRef+stepBytesRF))
 
                            );
 
                    s6 = _mm_shuffle_epi32(s6, 0x44);
 
                    s7 = _mm_unpacklo_epi32(
 
                            _mm_cvtsi32_si128(*(unsigned int*)(pRef+2*stepBytesRF)),
 
                            _mm_cvtsi32_si128(*(unsigned int*)(pRef+3*stepBytesRF))
 
                            );
 
                    s7 = _mm_shuffle_epi32(s7, 0x44);
 
 
 
                    s0 = _mm_adds_epu16(_mm_sad_epu8(s6, s8), _mm_sad_epu8(s7, s9));
 
                    s1 = _mm_adds_epu16(
 
                            _mm_sad_epu8(s6, s10), 
 
                            _mm_sad_epu8(s7, s11)
 
                            );
 
 
 
                    temSum = _mm_extract_epi16(s0,0);
 
                    if (temSum < lowSum[0])
 
                    {
 
                       lowSum[0] = temSum;
 
                       *matchBlock = j;
 
                       *(matchBlock+1) = i;
 
                    }
 
 
 
                    ...
 
 
 
                    //Repeat for the remaining 3 sums
 
 
 
                    ...
 
               }
 
        }
 
        return 0;
 
}

Команда MPSADBW решения Intel SSE4 вычисляет восемь сумм разности в одной команде. Каждая сумма вычисляется из абсолютной разности пары 4-байтовых беззнаковых целых чисел. На рис.1 показано вычисление 8 сумм с помощью команды MPSADBW. В качестве третьего операнда команда MPSADBW использует ближайшую сумму. Биты 0 и 1 ближайшей суммы используются для выбора одной из четырех групп по 4 байта из исходного операнда. Бит 2 ближайшей суммы используется для выбора одной из двух групп по 11 байт из операнда назначения. На рис.1 окошко с затемненным непрерывным контуром указывает на выбранный блок в этой иллюстрации. Окошко с темным прерывистым контуром указывает на другие блоки, которые могут быть выбраны при указании соответствующих битов в ближайшей сумме.

Хотя блок 4x4 является идеальным по размеру блоком для использования данный команды, другие размеры, как, например, 8x4 или 8x8, также могут быть оптимизированы при использовании команды. Биты 0 и 1 ближайшего значения можно использовать, чтобы выбрать другую 4-пиксельную группу для ее включения в вычисление, как показано в листинге 3. Следовательно, чтобы вычислить суммы абсолютной разности для размеров блока, которые являются множителями 4x4, мы повторяем команду MPSADBW, используя каждый раз другое ближайшее значение, а затем складываем результаты из нескольких операций MPSADBW и применяем команду PADDUSW, чтобы получить окончательный итог.

Рис. 1. Восемь сумм, вычисленных с помощью команды MPSADBW

После определения сумм команда PHMINPOSUW позволяет определить минимум из вычисленных SAD. Образец кода, использующего набор команд Intel SSE4 для поиска движения, приведен в листинге 3.

Листинг 3. Intel® SSE4 – Оптимизированная функция целочисленного блочного сравнения, осуществляющая поиск совпадающих блоков для четырех блоков 4x4 при каждом вызове.

 
//finds matching blocks for four 4x4 blocks in each call
 
int blockMatch4x4SSE4(const unsigned char* refFrame, int stepBytesRF, const unsigned
 
char* curBlock, int stepBytesCB, int* matchBlock, int frameWidth, int frameHeight)
 
{
 
   unsigned int lowSum[4] = {UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX};
 
   unsigned int temSum = 0;
 
   int blockHeight = 4;
 
   int blockWidth = 4;
 
   int k;
 
   const unsigned char *pRef, *pCur;
 
   __m128i s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
 
 
 
   pCur = curBlock;
 
   s0 = _mm_loadu_si128((__m128i*)pCur);
 
   s1 = _mm_loadu_si128((__m128i*)(pCur+stepBytesCB));
 
   s2 = _mm_loadu_si128((__m128i*)(pCur+2*stepBytesCB));
 
   s3 = _mm_loadu_si128((__m128i*)(pCur+3*stepBytesCB));
 
 
 
   s8 = _mm_unpacklo_epi32(s0, s1);
 
   s9 = _mm_unpacklo_epi32(s2, s3);
 
   s10 = _mm_unpackhi_epi32(s0, s1);
 
   s11 = _mm_unpackhi_epi32(s2, s3);
 
 
 
   for (int i=0; i<=frameHeight-blockHeight; i++)
 
   {
 
      int j = 0;
 
      for (j=0; j<=frameWidth-16; j+=8)
 
      {
 
         pCur = curBlock;
 
         pRef = refFrame+i*stepBytesRF+j;
 
         s2 = _mm_setzero_si128();
 
         s3 = _mm_setzero_si128();
 
         s4 = _mm_setzero_si128();
 
         s5 = _mm_setzero_si128();
 
         for (k=0; k<blockHeight; k++)
 
         {
 
            s0 = _mm_loadu_si128((__m128i*)pRef);
 
            s1 = _mm_loadu_si128((__m128i*)pCur);
 
            s2 = _mm_adds_epu16(s2, _mm_mpsadbw_epu8(s0, s1, 0));
 
            s3 = _mm_adds_epu16(s3, _mm_mpsadbw_epu8(s0, s1, 1));
 
            s4 = _mm_adds_epu16(s4, _mm_mpsadbw_epu8(s0, s1, 2));
 
            s5 = _mm_adds_epu16(s5, _mm_mpsadbw_epu8(s0, s1, 3));
 
            pCur+=stepBytesCB;
 
            pRef+=stepBytesRF;
 
         }
 
         s6 = _mm_minpos_epu16(s2);
 
         temSum = _mm_extract_epi16(s6,0);
 
         if (temSum < lowSum[0])
 
         {
 
            lowSum[0] = temSum;
 
            k = _mm_extract_epi16(s6,1);
 
            *matchBlock = j+k;
 
            *(matchBlock+1) = i;
 
         }
 
 
 
         ...
 
 
 
         // Repeat for the remaining 3 sums
 
 
 
         ...
 
      }
 
      for (; j<=frameWidth-blockWidth; j++)
 
      {
 
 
 
         ...
 
 
 
         // Use SSE2 code to process the rest, the edge case.
 
 
 
         ...
 
      }
 
   }
 
   return 0;
 
}

Результаты

Ускорения, полученные путем оптимизации, приведены в табл. 1. Результаты представлены как отношение количества циклов к подсчитанной сумме абсолютных разностей (SAD) всех пикселов сравниваемых блоков [2]. В столбце «Увеличение скорости» представлены коэффициенты, вычисленные на основании результатов использования SSE2. Были протестированы три различных размера блоков: 4x4, 8x8 и 16x16. С первого взгляда может показаться, что команда MPSADBW может быть применена только к блокам шириной 4 байта. Тем не менее, эту команду также можно использовать для вычисления сумм абсолютной разности блоков шириной 8 и 16 байтов, как показано в Приложении D и F.

При создании этого кода использовалась бета-версия компилятора Intel® 10.0.018. Использовались флажки компилятора «O2» и «QxS». «QxS» - это новый флаг компилятора, позволяющий создавать оптимизированный код специально для Penryn. Разница между увеличением скорости при использовании функции, оптимизированной под SSE2, и функции, оптимизированной под SSE4, варьируется между 1,6 и 3,8. Помимо значительного ускорения процессов при использовании SSE4 дополнительные преимущества можно получить с помощью многопоточности (Рис. 2).

Образец кода

Циклы / SAD блока

Увеличение скорости

Блок 4x4

C++

54.84

SSE2

4.32

1.00

Intel SSE4

2.71

1.59

Блок 8x8

C++

180.55

SSE2

25.29

1.00

Intel SSE4

6.73

3.83

Блок 16x16

C++

173.01

SSE2

71.42

1.00

Intel SSE4

26.86

2.66


Таблица 1. Количество циклов на SAD блока

Рис. 2. Диаграмма, демонстрирующая увеличение скорости в сравнении с однопоточной версией SSE2

Заключение

Использование команд Intel SSE4 увеличивает производительность функций поиска целочисленных векторов движения при оценке движения. При использовании базовых функций SSE2 производительность увеличивается от 1,6 до 3,8 раз. В некоторых версиях, включая функцию для размера блока 8x8, использующего команду Intel SSE4, функция написана таким образом, что позволяет находить вектор движения для нескольких блоков при каждом вызове функции. Это позволяет занять все сегменты данных в 128-разрядных реестрах и максимально повысить пропускную способность для операций с векторами. Из этих примерах видно, что необходимо специально адаптировать код для каждого размера блока, чтобы добиться максимального преимущества от 128-разрядных операций над векторами. Мы также показали, что команду MPSADBW можно использовать, чтобы вычислять суммы абсолютной разности не только блоков шириной 4 байта, но также и блоков шириной 8 и 16 байтов. Следовательно, возможно использование данной команды в алгоритмах с блоками разных размеров. Повышение производительности обеспечивается не только благодаря командам Intel SSE4, но и многопоточным процессам. Команды Intel SSE4 позволяют разработчикам оптимизировать приложения и добиться максимальной производительности в таких приложениях, требующих большой вычислительной мощности, как видео кодеки.

A. SSE2 – Оптимизированная функция для блоков 4x4

 
//finds matching blocks for four 4x4 blocks in each call

int blockMatch4x4SSE2(const unsigned char* refFrame, int stepBytesRF, const unsigned 

char* curBlock, int stepBytesCB, int* matchBlock, int frameWidth, int frameHeight)

{

   unsigned int lowSum[4] = {UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX};

   unsigned int temSum = 0;

   int blockHeight = 4;

   int blockWidth = 4;

   const unsigned char *pRef, *pCur;

   __m128i s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;

 

   pCur = curBlock;

   s0 = _mm_loadu_si128((__m128i*)pCur);

   s1 = _mm_loadu_si128((__m128i*)(pCur+stepBytesCB));

   s2 = _mm_loadu_si128((__m128i*)(pCur+2*stepBytesCB));

   s3 = _mm_loadu_si128((__m128i*)(pCur+3*stepBytesCB));

 

   s8 = _mm_unpacklo_epi32(s0, s1);

   s9 = _mm_unpacklo_epi32(s2, s3);

   s10 = _mm_unpackhi_epi32(s0, s1);

   s11 = _mm_unpackhi_epi32(s2, s3);

 

   for (int i=0; i<=frameHeight-blockHeight; i++)

   {

      for (int j=0; j<=frameWidth-blockWidth; j++)

      {

         pRef = refFrame+i*stepBytesRF+j;

 

         s6 = _mm_unpacklo_epi32(

                 _mm_cvtsi32_si128(*(unsigned int*)pRef), 

                 _mm_cvtsi32_si128(*(unsigned int*)(pRef+stepBytesRF))

                 );

         s6 = _mm_shuffle_epi32(s6, 0x44);

         s7 = _mm_unpacklo_epi32(

                 _mm_cvtsi32_si128(*(unsigned int*)(pRef+2*stepBytesRF)),

                 _mm_cvtsi32_si128(*(unsigned int*)(pRef+3*stepBytesRF))

                            );

         s7 = _mm_shuffle_epi32(s7, 0x44);

 

         s0 = _mm_adds_epu16(_mm_sad_epu8(s6, s8), _mm_sad_epu8(s7, s9));

         s1 = _mm_adds_epu16(

                 _mm_sad_epu8(s6, s10), 

                 _mm_sad_epu8(s7, s11)

                 );

 

         temSum = _mm_extract_epi16(s0,0);

         if (temSum < lowSum[0])

         {

            lowSum[0] = temSum;

            *matchBlock = j;

            *(matchBlock+1) = i;

         }

 

         temSum = _mm_extract_epi16(s0,4);

         if (temSum < lowSum[1])

         {

            lowSum[1] = temSum;

            *(matchBlock+2) = j;

            *(matchBlock+3) = i;

         }

 

         temSum = _mm_extract_epi16(s1,0);

         if (temSum < lowSum[2])

         {

            lowSum[2] = temSum;

            *(matchBlock+4) = j;

            *(matchBlock+5) = i;

         }

 

         temSum = _mm_extract_epi16(s1,4);

         if (temSum < lowSum[3])

         {

            lowSum[3] = temSum;

            *(matchBlock+6) = j;

            *(matchBlock+7) = i;

         }

      }

   }

   return 0;

}

B. Intel® SSE4 – Оптимизированная функция для блоков 4x4

 
//finds matching blocks for four 4x4 blocks in each call 
int blockMatch4x4SSE4(const unsigned char* refFrame, 
int stepBytesRF, const unsigned char* curBlock, int stepBytesCB, 
int* matchBlock, int frameWidth, int frameHeight) 
{ 
   unsigned int lowSum[4] = {UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX}; 
   unsigned int temSum = 0; 
   int blockHeight = 4;  
   int blockWidth = 4; 
   int k; 
   const unsigned char *pRef, *pCur; 
   __m128i s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
   
 
 
 
   pCur = curBlock; 
   s0 = _mm_loadu_si128((__m128i*)pCur); 
   s1 = _mm_loadu_si128((__m128i*)(pCur+stepBytesCB)); 
   s2 = _mm_loadu_si128((__m128i*)(pCur+2*stepBytesCB)); 
   s3 = _mm_loadu_si128((__m128i*)(pCur+3*stepBytesCB)); 
   s8 = _mm_unpacklo_epi32(s0, s1); 
   s9 = _mm_unpacklo_epi32(s2, s3); 
   s10 = _mm_unpackhi_epi32(s0, s1); 
   s11 = _mm_unpackhi_epi32(s2, s3);
   for (int i=0; i<=frameHeight-blockHeight; i++) 
   { 
      int j = 0; 
      for (j=0; j<=frameWidth-16; j+=8) 
      { 
         pCur = curBlock; 
         pRef = refFrame+i*stepBytesRF+j; 
         s2 = _mm_setzero_si128(); 
         s3 = _mm_setzero_si128(); 
         s4 = _mm_setzero_si128(); 
         s5 = _mm_setzero_si128(); 
         for (k=0; k< lowSum[0]) 
         { 
            lowSum[0] = temSum; 
            k = _mm_extract_epi16(s6,1); 
            *matchBlock = j+k; 
            *(matchBlock+1) = i; 
         } 
         s6 = _mm_minpos_epu16(s3); 
         temSum = _mm_extract_epi16(s6,0); 
         if (temSum < lowSum[1]) 
         { 
            lowSum[1] = temSum; 
            k = _mm_extract_epi16(s6,1); 
            *(matchBlock+2) = j+k; 
            *(matchBlock+3) = i; 
         } 
         s6 = _mm_minpos_epu16(s4); 
         temSum = _mm_extract_epi16(s6,0); 
         if (temSum < lowSum[2]) 
         { 
            lowSum[2] = temSum; 
            k = _mm_extract_epi16(s6,1); 
            *(matchBlock+4) = j+k; 
            *(matchBlock+5) = i; 
         } 
         s6 = _mm_minpos_epu16(s5); 
         temSum = _mm_extract_epi16(s6,0); 
         if (temSum < lowSum[3]) 
         { 
            lowSum[3] = temSum; 
            k = _mm_extract_epi16(s6,1); 
            *(matchBlock+6) = j+k; 
            *(matchBlock+7) = i; 
         } 
      } 
      for (; j<=frameWidth-blockWidth; j++) 
      { 
         pRef = refFrame+i*stepBytesRF+j; 
         s6 = _mm_unpacklo_epi32( 
                 _mm_cvtsi32_si128(*(unsigned int*)pRef), 
                 _mm_cvtsi32_si128(*(unsigned int*)(pRef+stepBytesRF))
                 ); 
         s6 = _mm_shuffle_epi32(s6, 0x44); 
         s7 = _mm_unpacklo_epi32( 
                 _mm_cvtsi32_si128(*(unsigned int*)(pRef+2*stepBytesRF)),
                 _mm_cvtsi32_si128(*(unsigned int*)(pRef+3*stepBytesRF))
                 ); 
         s7 = _mm_shuffle_epi32(s7, 0x44); 
         s0 = _mm_adds_epu16(_mm_sad_epu8(s6, s8), _mm_sad_epu8(s7, s9)); 
         s1 = _mm_adds_epu16( 
                 _mm_sad_epu8(s6, s10), 
                 _mm_sad_epu8(s7, s11) 
                 ); 
         temSum = _mm_extract_epi16(s0,0); 
         if (temSum < lowSum[0]) 
         { 
            lowSum[0] = temSum; 
            *matchBlock = j; 
            *(matchBlock+1) = i; 
         } 
         temSum = _mm_extract_epi16(s0,4); 
         if (temSum < lowSum[1]) 
         { 
            lowSum[1] = temSum; 
            *(matchBlock+2) = j; 
            *(matchBlock+3) = i; 
         } 
         temSum = _mm_extract_epi16(s1,0); 
         if (temSum < lowSum[2]) 
         { 
            lowSum[2] = temSum; 
            *(matchBlock+4) = j; 
            *(matchBlock+5) = i; 
         } 
         temSum = _mm_extract_epi16(s1,4); 
         if (temSum < lowSum[3]) Kessler, Annemone
         { 
            lowSum[3] = temSum; 
            *(matchBlock+6) = j; 
            *(matchBlock+7) = i; 
         } 
      } 
   } 
   return 0; 
}

C. SSE2 – Оптимизированная функция для блоков 8x8

 
int blockMatch8x8SSE2(const unsigned char* refFrame, int stepBytesRF, 
const unsigned char* curBlock, int stepBytesCB, int* matchBlock, 
int frameWidth, int frameHeight)
{
 
   unsigned int lowSum = UINT_MAX;
 
   unsigned int temSum[2] = {0, 0};
 
   int blockHeight = 8;
 
   int blockWidth = 8;
 
   const unsigned char *pRef, *pCur;
 
   __m128i s0, s1, s2, s3, s4, s5, s6, s7, s8;
 
 
 
   pCur = curBlock;
 
   s0 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)pCur), 0x44);
 
   s1 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+stepBytesCB)), 0x44);
 
   s2 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+2*stepBytesCB)), 0x44);
 
   s3 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+3*stepBytesCB)), 0x44);
 
   s4 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+4*stepBytesCB)), 0x44);
 
   s5 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+5*stepBytesCB)), 0x44);
 
   s6 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+6*stepBytesCB)), 0x44);
 
   s7 = _mm_shuffle_epi32(_mm_loadu_si128((__m128i*)(pCur+7*stepBytesCB)), 0x44);
 
 
 
   for (int i=0; i<=frameHeight-blockHeight; i++)
 
   {
 
      int j;
 
      for (j=0; j<frameWidth-16; j+=16)
 
      {
 
         for (int k=0; k<8; k++)
 
         {
 
            pRef = refFrame+i*stepBytesRF+j+k;
 
 
 
            s8 = _mm_sad_epu8(s0, _mm_loadu_si128((__m128i*)pRef));
 
            s8 = _mm_adds_epu16(s8, 
 
                    _mm_sad_epu8(s1, 
 
                    _mm_loadu_si128((__m128i*)(pRef+stepBytesRF))));
 
            s8 = _mm_adds_epu16(s8,
 
                    _mm_sad_epu8(s2, 
 
                    _mm_loadu_si128((__m128i*)(pRef+2*stepBytesRF))));
 
            s8 = _mm_adds_epu16(s8, 
 
                    _mm_sad_epu8(s3, 
 
                    _mm_loadu_si128((__m128i*)(pRef+3*stepBytesRF))));
 
            s8 = _mm_adds_epu16(s8, 
 
                    _mm_sad_epu8(s4,
 
                    _mm_loadu_si128((__m128i*)(pRef+4*stepBytesRF))));
 
            s8 = _mm_adds_epu16(s8, 
 
                    _mm_sad_epu8(s5, 
 
                    _mm_loadu_si128((__m128i*)(pRef+5*stepBytesRF))));
 
            s8 = _mm_adds_epu16(s8, 
 
                    _mm_sad_epu8(s6, 
 
                    _mm_loadu_si128((__m128i*)(pRef+6*stepBytesRF))));
 
            s8 = _mm_adds_epu16(s8, 
 
                    _mm_sad_epu8(s7, 
 
                    _mm_loadu_si128((__m128i*)(pRef+7*stepBytesRF))));
 
            temSum[0] = _mm_extract_epi16(s8,0);
 
            temSum[1] = _mm_extract_epi16(s8,4);
 
            if (temSum[0] <= temSum[1] && temSum[0] < lowSum)
 
            {
 
               lowSum = temSum[0];
 
               *matchBlock = j+k;
 
               *(matchBlock+1) = i;
 
            }
 
            else if (temSum[1] < lowSum)
 
            {
 
               lowSum = temSum[1];
 
               *matchBlock = j+k+8;
 
               *(matchBlock+1) = i;
 
            }
 
         }
 
      }     
 
      for (; j<=frameWidth-blockWidth; j++)
 
      {
 
         pRef = refFrame+i*stepBytesRF+j;
 
 
 
         s8 = _mm_sad_epu8(s0, _mm_loadl_epi64((__m128i*)pRef));
 
         s8 = _mm_adds_epu16(s8, 
 
                  _mm_sad_epu8(s1, 
 
                  _mm_loadl_epi64((__m128i*)(pRef+stepBytesRF))));
 
         s8 = _mm_adds_epu16(s8, 
 
                _mm_sad_epu8(s2, 
 
                _mm_loadl_epi64((__m128i*)(pRef+2*stepBytesRF))));
 
         s8 = _mm_adds_epu16(s8, 
 
                 _mm_sad_epu8(s3, 
 
                 _mm_loadl_epi64((__m128i*)(pRef+3*stepBytesRF))));
 
         s8 = _mm_adds_epu16(s8, 
 
                 _mm_sad_epu8(s4, 
 
                 _mm_loadl_epi64((__m128i*)(pRef+4*stepBytesRF))));
 
         s8 = _mm_adds_epu16(s8, 
 
                 _mm_sad_epu8(s5, 
 
                 _mm_loadl_epi64((__m128i*)(pRef+5*stepBytesRF))));
 
         s8 = _mm_adds_epu16(s8, 
 
                 _mm_sad_epu8(s6, 
 
                 _mm_loadl_epi64((__m128i*)(pRef+6*stepBytesRF))));
 
         s8 = _mm_adds_epu16(s8, 
 
                 _mm_sad_epu8(s7, 
 
                 _mm_loadl_epi64((__m128i*)(pRef+7*stepBytesRF))));
 
 
 
         temSum[0] = _mm_extract_epi16(s8,0);
 
         if (temSum[0] < lowSum)
 
         {
 
            lowSum = temSum[0];
 
            *matchBlock = j;
 
            *(matchBlock+1) = i;
 
         }
 
      }
 
   }
 
   return 0;
 
}

D. Intel® SSE4 – Оптимизированная функция для блоков 8x8

 
//searches two 8x8 blocks in each call
 
int blockMatch8x8SSE4(const unsigned char* refFrame, int stepBytesRF, const unsigned
 
char* curBlock, int stepBytesCB, int* matchBlock, int frameWidth, int frameHeight)
 
{
 
   unsigned int lowSum[2] = {UINT_MAX, UINT_MAX};
 
   unsigned int temSum = 0;
 
   int blockHeight = 8;
 
   int blockWidth = 8;
 
   int k;
 
   const unsigned char *pRef, *pCur;
 
   __m128i s0, s1, s2, s3, s4, s5, s6;
 
 
 
   for (int i=0; i<=frameHeight-blockHeight; i++)
 
   {
 
      int j=0;
 
      for (j=0; j<=frameWidth-16; j+=8)
 
      {
 
         pCur = curBlock;
 
         pRef = refFrame+i*stepBytesRF+j;
 
         s2 = _mm_setzero_si128();
 
         s3 = _mm_setzero_si128();
 
         s4 = _mm_setzero_si128();
 
         s5 = _mm_setzero_si128();
 
         for (k=0; k<blockHeight; k++)
 
         {
 
            s0 = _mm_loadu_si128((__m128i*)pRef);
 
            s1 = _mm_loadu_si128((__m128i*)pCur);
 
            s2 = _mm_adds_epu16(s2, _mm_mpsadbw_epu8(s0, s1, 0));
 
            s3 = _mm_adds_epu16(s3, _mm_mpsadbw_epu8(s0, s1, 5));
 
            s4 = _mm_adds_epu16(s4, _mm_mpsadbw_epu8(s0, s1, 2));
 
            s5 = _mm_adds_epu16(s5, _mm_mpsadbw_epu8(s0, s1, 7));
 
            pCur+=stepBytesCB;
 
            pRef+=stepBytesRF;
 
         }
 
         s6 = _mm_minpos_epu16(_mm_adds_epu16(s2, s3));
 
         temSum = _mm_extract_epi16(s6,0);
 
         if (temSum < lowSum[0])
 
         {
 
            lowSum[0] = temSum;
 
            k = _mm_extract_epi16(s6,1);
 
            *matchBlock = j+k;
 
            *(matchBlock+1) = i;
 
         }
 
         s6 = _mm_minpos_epu16(_mm_adds_epu16(s4, s5));
 
         temSum = _mm_extract_epi16(s6,0);
 
         if (temSum < lowSum[1])
 
         {
 
            lowSum[1] = temSum;
 
            k = _mm_extract_epi16(s6,1);
 
            *(matchBlock+2) = j+k;
 
            *(matchBlock+3) = i;
 
         }
 
      }
 
 
 
      for (; j<=frameWidth-blockWidth; j++)
 
      {
 
         pCur = curBlock;
 
         pRef = refFrame+i*stepBytesRF+j;
 
 
 
         s2 = _mm_setzero_si128();
 
         for (k=0; k<blockHeight; k++)
 
         {
 
            s0 = _mm_loadl_epi64((__m128i*)pRef);
 
            s0 = _mm_shuffle_epi32(s0, 0x44);
 
            s1 = _mm_loadu_si128((__m128i*)pCur);
 
            s2 = _mm_adds_epu16(s2, _mm_sad_epu8(s0, s1));
 
 
 
            pCur+=stepBytesCB;
 
            pRef+=stepBytesRF;
 
         }
 
 
 
         temSum = _mm_extract_epi16(s2,0);
 
         if (temSum < lowSum[0])
 
         {
 
            lowSum[0] = temSum;
 
            *matchBlock = j;
 
            *(matchBlock+1) = i;
 
         }
 
         temSum = _mm_extract_epi16(s2,4);
 
         if (temSum < lowSum[1])
 
         {
 
            lowSum[1] = temSum;
 
            *(matchBlock+2) = j;
 
            *(matchBlock+3) = i;
 
         }
 
      }
 
   }
 
   return 0;
 
}

E. SSE2 – Оптимизированная функция для блоков 16x16

int blockMatch16x16SSE2(const unsigned char* refFrame, int stepBytesRF, const unsigned

char* curBlock, int stepBytesCB, int* matchBlock, int frameWidth, int frameHeight)

{

   unsigned int lowSum = UINT_MAX;

   unsigned int temSum = 0;

   int blockHeight = 16;

   int blockWidth = 16;

   const unsigned char *pRef, *pCur;

   __m128i s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16;

 

   pCur = curBlock;

   s0 = _mm_loadu_si128((__m128i*)pCur);

   s1 = _mm_loadu_si128((__m128i*)(pCur+stepBytesCB));

   s2 = _mm_loadu_si128((__m128i*)(pCur+2*stepBytesCB));

   s3 = _mm_loadu_si128((__m128i*)(pCur+3*stepBytesCB));

   s4 = _mm_loadu_si128((__m128i*)(pCur+4*stepBytesCB));

   s5 = _mm_loadu_si128((__m128i*)(pCur+5*stepBytesCB));

   s6 = _mm_loadu_si128((__m128i*)(pCur+6*stepBytesCB));

   s7 = _mm_loadu_si128((__m128i*)(pCur+7*stepBytesCB));

   s8 = _mm_loadu_si128((__m128i*)(pCur+8*stepBytesCB));

   s9 = _mm_loadu_si128((__m128i*)(pCur+9*stepBytesCB));

   s10 = _mm_loadu_si128((__m128i*)(pCur+10*stepBytesCB));

   s11 = _mm_loadu_si128((__m128i*)(pCur+11*stepBytesCB));

   s12 = _mm_loadu_si128((__m128i*)(pCur+12*stepBytesCB));

   s13 = _mm_loadu_si128((__m128i*)(pCur+13*stepBytesCB));

   s14 = _mm_loadu_si128((__m128i*)(pCur+14*stepBytesCB));

   s15 = _mm_loadu_si128((__m128i*)(pCur+15*stepBytesCB));

 

   for (int i=0; i<=frameHeight-blockHeight; i++)

   {

      for (int j=0; j<=frameWidth-blockWidth; j++)

      {

         pRef = refFrame+i*stepBytesRF+j;

 

         s16 = _mm_sad_epu8(s0, _mm_loadu_si128((__m128i*)pRef));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s1, 

                  _mm_loadu_si128((__m128i*)(pRef+stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s2, 

                  _mm_loadu_si128((__m128i*)(pRef+2*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s3, 

                  _mm_loadu_si128((__m128i*)(pRef+3*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s4, 

                  _mm_loadu_si128((__m128i*)(pRef+4*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s5, 

                  _mm_loadu_si128((__m128i*)(pRef+5*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s6, 

                  _mm_loadu_si128((__m128i*)(pRef+6*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s7, 

                  _mm_loadu_si128((__m128i*)(pRef+7*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s8, 

                  _mm_loadu_si128((__m128i*)(pRef+8*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s9, 

                  _mm_loadu_si128((__m128i*)(pRef+9*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s10, 

                  _mm_loadu_si128((__m128i*)(pRef+10*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s11, 

                  _mm_loadu_si128((__m128i*)(pRef+11*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s12, 

                  _mm_loadu_si128((__m128i*)(pRef+12*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s13, 

                  _mm_loadu_si128((__m128i*)(pRef+13*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s14, 

                  _mm_loadu_si128((__m128i*)(pRef+14*stepBytesRF))));

         s16 = _mm_adds_epu16(s16, 

                  _mm_sad_epu8(s15, 

                  _mm_loadu_si128((__m128i*)(pRef+15*stepBytesRF))));

 

         temSum = _mm_extract_epi16(s16,0) + _mm_extract_epi16(s16,4);

         if (temSum < lowSum)

         {

            lowSum = temSum;

            *matchBlock = j;

            *(matchBlock+1) = i;

         }

      }

   }

   return 0;

}


F. Intel® SSE4 – Оптимизированная функция для блоков 16x16

 
int blockMatch16x16SSE4(const unsigned char* refFrame, int stepBytesRF, const unsigned
 
char* curBlock, int stepBytesCB, int* matchBlock, int frameWidth, int frameHeight)
 
{
 
   unsigned int lowSum = UINT_MAX;
 
   unsigned int temSum = 0;
 
   int blockHeight = 16;
 
   int blockWidth = 16;
 
   int k;
 
   const unsigned char *pRef, *pCur;
 
   __m128i s0, s1, s2, s3, s4, s5, s6, s7;
 
 
 
   for (int i=0; i<=frameHeight-blockHeight; i++)
 
   {
 
      int j=0;
 
      for (j=0; j<=frameWidth-24; j+=8)
 
      {
 
         pCur = curBlock;
 
         pRef = refFrame+i*stepBytesRF+j;
 
         s3 = _mm_setzero_si128();
 
         s4 = _mm_setzero_si128();
 
         s5 = _mm_setzero_si128();
 
         s6 = _mm_setzero_si128();
 
         for (k=0; k<blockHeight; k++)



         {



            s0 = _mm_loadu_si128((__m128i*)pRef);



            s1 = _mm_loadu_si128((__m128i*)(pRef+8));



            s2 = _mm_loadu_si128((__m128i*)pCur);



            s3 = _mm_adds_epu16(s3, _mm_mpsadbw_epu8(s0, s2, 0));



            s4 = _mm_adds_epu16(s4, _mm_mpsadbw_epu8(s0, s2, 5));



            s5 = _mm_adds_epu16(s5, _mm_mpsadbw_epu8(s1, s2, 2));



            s6 = _mm_adds_epu16(s6, _mm_mpsadbw_epu8(s1, s2, 7));



            pCur+=stepBytesCB;



            pRef+=stepBytesRF;



         }



         s7 = _mm_adds_epu16(_mm_adds_epu16(s3, s4), _mm_adds_epu16(s5, s6));



         s7 = _mm_minpos_epu16(s7);



         temSum = _mm_extract_epi16(s7,0);



         if (temSum >< lowSum)
 
         {
 
            lowSum = temSum;
 
            k = _mm_extract_epi16(s7,1);
 
            *matchBlock = j+k;
 
            *(matchBlock+1) = i;
 
         }
 
      }
 
 
 
      for (; j<=frameWidth-blockWidth; j++)
 
      {
 
         pCur = curBlock;
 
         pRef = refFrame+i*stepBytesRF+j;
 
 
 
         s2 = _mm_setzero_si128();
 
         for (k=0; k<blockHeight; k++)



         {



            s0 = _mm_loadu_si128((__m128i*)pRef);



            s1 = _mm_loadu_si128((__m128i*)pCur);



            s2 = _mm_adds_epu16(s2, _mm_sad_epu8(s0, s1));



 



            pCur+=stepBytesCB;



            pRef+=stepBytesRF;



 



         }



 



         temSum = _mm_extract_epi16(s2,0) + _mm_extract_epi16(s2,4);



         if (temSum >< lowSum)
 
         {
 
            lowSum = temSum;
 
            *matchBlock = j;
 
            *(matchBlock+1) = i;
 
         }
 
      }
 
   }
 
   return 0;
 
}

[1] Дополнительная информация о команде PSADBW представлена в Руководстве разработчика ПО для архитектуры Intel® 64 и IA-32, том 2B.

[2] Размер каждой области поиска составлял 128x128 пикселей. Для областей поиска такого размера количество подсчитанных блоков SAD составляло 15625 для каждого блока 4x4, 14641 для каждого блока 8x8 и 12769 для каждого блока 16x16. При оценке увеличения скорости версия SSE2 используется в качестве параметра для сравнения. Код создан с помощью бета-версии компилятора Intel Compiler 10.0.018 на базе Microsoft* Visual Studio* 2005. Флаги компилятора O2 и QxS использовались при создании этих функций. Тестовая система состояла из четырехъядерного процессора Intel® для настольных ПК на базе 45-нанометровой технологии (Yorkfield), системной платы «Bearlake» предварительного выпуска и памяти объемом 2 ГБ DDR2 RAM PC2-6400 (400 МГц). Использовалась операционная система Windows* XP Professional с пакетом сервисных программ Service Pack 2

Пожалуйста, обратитесь к странице Уведомление об оптимизации для более подробной информации относительно производительности и оптимизации в программных продуктах компании Intel.

Комментарии

Аватар пользователя Ivan Kiselev

Функция blockMatch4x4SSE4() скопирована из оригинального текста с ошибками - потеряна часть кода