SSE intrinsics - сравнение if / else оптимизация - PullRequest
8 голосов
/ 24 января 2012

Я пытался оптимизировать некоторый код, который обрабатывает необработанные данные пикселей. В настоящее время реализация кода на C ++ слишком медленная, поэтому я пытался обосновать использование встроенных функций SSE (SSE / 2/3, не использующих 4) в MSVC 2008. Учитывая, что я впервые копаю в этом минимуме, я мы добились хорошего прогресса.

К сожалению, я пришел к конкретному коду, который застрял:

//Begin bad/suboptimal SSE code
__m128i vnMask  = _mm_set1_epi16(0x0001);
__m128i vn1     = _mm_and_si128(vnFloors, vnMask);

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++)
{
    bool bIsEvenFloor   = vn1.m128i_u16[m]==0;

    vnPxChroma.m128i_u16[m] = 
        m%2==0 
            ?
        (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m])
            :
        (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]);
}

В настоящее время я по умолчанию использую реализацию C ++ для этого раздела, потому что не могу понять, как это можно оптимизировать с помощью SSE - я нахожу встроенные функции SSE для сравнений. немного хитро.

Любые предложения / советы будут высоко ценится.

EDIT: Эквивалентный код C ++, который обрабатывает один пиксель за раз, будет:

short pxCl=0, pxFl=0;
short uv=0; // chroma component of pixel
short y=0;  // luma component of pixel

for(int i = 0; i < end-of-line, ++i)
{
    //Initialize pxCl, and pxFL
    //...

    bool bIsEvenI       = (i%2)==0;
    bool bIsEvenFloor   = (m_pnDistancesFloor[i] % 2)==0;

    uv = bIsEvenI ==0 
        ?
    (bIsEvenFloor ? pxCl : pxFl)
        :
    (bIsEvenFloor ? pxFl : pxCl);

    //Merge the Y/UV of the pixel;
    //...
}

По сути, я делаю нелинейное растяжение края от 4: 3 до 16: 9.

Ответы [ 2 ]

7 голосов
/ 24 января 2012

Хорошо, поэтому я не знаю, что делает этот код, однако я знаю, что вы спрашиваете, как оптимизировать операторы ternery и заставить эту часть кода работать только в SSE.В качестве первого шага я бы рекомендовал попробовать подход, использующий целочисленные флаги и умножение, чтобы избежать условного оператора.Например:

Этот раздел

for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m++)
{
    bool bIsEvenFloor   = vn1.m128i_u16[m]==0;      

    vnPxChroma.m128i_u16[m] = m%2==0 ?  
      (bIsEvenFloor ? vnPxCeilChroma.m128i_u16[m] : vnPxFloorChroma.m128i_u16[m])  : 
      (bIsEvenFloor ? vnPxFloorChroma.m128i_u16[m] : vnPxCeilChroma.m128i_u16[m]); 
}

синтаксически эквивалентен этому

// DISCLAIMER: Untested both in compilation and execution

// Process all m%2=0 in steps of 2
for(int m=0; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2)
{
    // This line could surely pack muliple u16s into one SSE2 register
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
    uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

    // This line could surely perform an SSE2 multiply across multiple registers
    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
                              iIsOddFloor * vnPxFloorChroma.m128i_u16[m]
}

// Process all m%2!=0 in steps of 2
for(int m=1; m < PBS_SSE_PIXELS_PROCESS_AT_ONCE; m+=2)
{
    uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
    uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

    vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxFloorChroma.m128i_u16[m] + 
                              iIsOddFloor * vnPxCeilChroma.m128i_u16[m]
}

В основном, разбившись на два цикла, вы теряете повышение производительности доступа к последовательной памяти, ноотбросьте операцию по модулю и два условных оператора.

Теперь вы говорите, что заметили, что в каждом цикле есть два логических оператора, а также умножения , которые я мог бы добавить, не являются внутренними реализациями SSE .Что хранится в вашем массиве vn1.m123i_u16 []?Это только нули и единицы?Если это так, вам не нужна эта часть, и вы можете покончить с ней.Если нет, можете ли вы нормализовать ваши данные в этом массиве, чтобы иметь только нули и единицы?Если массив vn1.m123i_u16 содержит только единицы и нули, то этот код становится

uint16 iIsOddFloor  = vn1.m128i_u16[m]
uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

Вы также заметите, что я не использую умножения SSE для выполнения isEvenFloor * vnPx... part и для хранения iIsEvenFloor и iIsOddFloor регистров.Извините, я не могу вспомнить встроенные функции SSE для u16 умножения / регистрации сверху, но, тем не менее, я надеюсь, что этот подход полезен.Некоторые оптимизации, на которые вы должны обратить внимание:

// This line could surely pack muliple u16s into one SSE2 register
uint16 iIsOddFloor = vn1.m128i_u16[m] & 0x1 // If u16[m] == 0, result is 0
uint16 iIsEvenFloor = iIsOddFloor ^ 0x1 // Flip 1 to 0, 0 to 1

// This line could surely perform an SSE2 multiply across multiple registers
vnPxChroma.m128i_u16[m] = iIsEvenFloor * vnPxCeilChroma.m128i_u16[m] + 
                          iIsOddFloor * vnPxFloorChroma.m128i_u16[m] 

В этом разделе кода, который вы опубликовали, и мое изменение, мы все еще не в полной мере используем встроенные функции SSE1 / 2/3, но это может обеспечитьнекоторые моменты о том, как это можно сделать (как векторизовать код).

Наконец, я бы сказал, чтобы проверить все.Запустите приведенный выше код без изменений и профилируйте его, прежде чем вносить изменения и снова выполнять профилирование.Фактические показатели производительности могут вас удивить!


Обновление 1 :

Я изучил документацию Intel SIMD Intrinsics , чтобы выбрать соответствующие встроенные функции, которые могут быть полезныза это.В частности, рассмотрим битовые XOR, AND и MULT / ADD

__ m128 Типы данных
Тип данных __m128i может содержать шестнадцать 8-битных, восемь 16-битных,четыре 32-разрядных или два 64-разрядных целочисленных значения.

__m128i _mm_add_epi16 (__m128i a, __m128i b)
Добавьте 8-битные целые числа со знаком или без знака в a к8 16-разрядных целых чисел со знаком или без знака в b

__ m128i _mm_mulhi_epu16 (__m128i a, __m128i b)
Умножает 8 беззнаковых 16-разрядных целых чисел на a на 8 беззнаковых 16-битные целые числа из b.Устанавливает верхние 16-битные 8-битные результаты без знака

R0 = hiword (a0 * b0)
R1 = hiword (a1 * b1)
R2 = hiword (a2 * b2)
R3 = hiword (a3 * b3)
..
R7 = hiword (a7 * b7)

__ m128i _mm_mullo_epi16 (__ m128i a, __m128i b)
Умножает 8-значные или беззнаковые 16-разрядные целые числа из a на 8-значные или беззнаковые 16-разрядные целые числа из b.Устанавливает верхние 16 битов 8-разрядных или беззнаковых 32-битных результатов

R0 = loword (a0 * b0)
R1 = loword (a1 * b1)
R2 = loword (a2* b2)
R3 = loword (a3 * b3)
..
R7 = loword (a7 * b7)

__m128i _mm_and_si128 (__m128i a, __m128i b)
Выполнить побитовое И 128-разрядного значения в m1 со 128-разрядным значением в м2.

__m128i _mm_andnot_si128 (__m128i a, __m128i b)
Вычисленияпобитовое И 128-битного значения в b и побитовое НЕ 128-битного значения в.

__ m128i _mm_xor_si128 (__m128i a, __m128i b)
Выполнить aбитовое XOR 128-битного значения в m1 с 128-битным значением в m2.

ТАКЖЕ из вашего примера кода для справки

uint16 u1 = u2 = u3 ... = u15 =0x1
__m128i vnMask = _mm_set1_epi16 (0x0001);// Устанавливает 8-значные 16-разрядные целые значения со знаком.

uint16 vn1 [i] = vnFloors [i] & 0x1
__m128i vn1 = _mm_and_si128 (vnFloors, vnMask); // Вычисляет побитовое И 128-битного значения в а и 128-битного значения в b.

2 голосов
/ 25 января 2012

Andrew ваши предложения ведут меня по пути к почти оптимальному решению.

Используя комбинацию таблицы истинности и карты Карно, я обнаружил, что код

 uv = bIsEvenI ==0 
    ?
(bIsEvenFloor ? pxCl : pxFl)
    :
(bIsEvenFloor ? pxFl : pxCl);

сводится к функции! Xor (не xor). С тех пор я смог использовать векторизацию SSE для оптимизации решения:

//Use the mask with bit AND to check if even/odd
__m128i vnMask              = _mm_set1_epi16(0x0001);

//Set the bit to '1' if EVEN, else '0'
__m128i vnFloorsEven        = _mm_andnot_si128(vnFloors, vnMask);
__m128i vnMEven             = _mm_set_epi16
    (
        0,  //m==7
        1,
        0,
        1,
        0,
        1,
        0,  //m==1
        1   //m==0
    );


// Bit XOR the 'floor' values and 'm'
__m128i vnFloorsXorM        = _mm_xor_si128(vnFloorsEven, vnMEven);

// Now perform our bit NOT
__m128i vnNotFloorsXorM     = _mm_andnot_si128(vnFloorsXorM, vnMask);

// This is the C++ ternary replacement - using multipilaction
__m128i vnA                 = _mm_mullo_epi16(vnNotFloorsXorM, vnPxFloorChroma);
__m128i vnB                 = _mm_mullo_epi16(vnFloorsXorM, vnPxCeilChroma);

// Set our pixels - voila!
vnPxChroma                  = _mm_add_epi16(vnA, vnB);

Спасибо за помощь ...

...