2012-01-24 2 views
8

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

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

//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 для сравнения немного сложны.

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

РЕДАКТИРОВАТЬ: эквивалент 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

SSE встроенных функций, трудно читать. Не могли бы вы добавить несколько комментариев/эквивалентных блоков кода C++, чтобы объяснить этот раздел? –

+0

Что вы хотите сделать для этого кода? – ronag

+0

Я немного озадачен этим фрагментом (загадочными идентификаторами и без контекста), но почему бы вам не заменить сравнение с умножением и добавлением? – zrxq

ответ

7

Итак, я не знаю, что делает этот код, однако я знаю, что вы спрашиваете, как оптимизировать операторов 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-intrinsics для 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] 

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

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


Update 1:

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

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

__m128i _mm_add_epi16 (__ m128i а, __m128i б)
Добавить 8 знаком или без знака 16-битных чисел в к 8 знаком или без знака 16-битных чисел в б

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

R0 = HIWORD (а0 * b0)
R1 = HIWORD (а1 * б1)
R2 = HIWORD (а2 * b2)
R3 = HIWORD (а3 * б3)
..
R 7 = HIWORD (а7 * b7)

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

R0 = LOWORD (a0 * b0)
R1 = LOWORD (а1 * б1)
R2 = LOWORD (а2 * b2)
R3 = LOWORD (а3 * б3)
..
R 7 = LOWORD (а7 * b7)

__m128i _mm_and_si128 (__ m128i а, __m128i б)
Выполнить побитовое И 128-бит значение в m1 с 128-битным значением в м2.

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

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

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

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-битного значения в a и 128-битное значение в b.

+0

Можно ли использовать побитовое И вместо умножения? – zrxq

+0

Спасибо, я уже разбил реализацию C++ на две (2) отдельные для циклов, как вы предложили. Я не рассматривал использование умножения/добавления для сравнения. Часть, на которую я стремлюсь, состоит в том, чтобы объединить два для циклов в один (1) набор инструкций. – ZeroDefect

+0

@zrxd да Я просто понял, что, отредактировал. Позаботьтесь, чтобы взглянуть? –

2

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

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

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

сводился, чтобы быть! Функцией 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); 

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

+0

Вау! Хорошо сделано и хорошая работа для публикации решения! Из интереса, какова производительность, например, версии SSE версии и кода на основе ванили C++? 'таблица истинности и карта карты 'Нравится. Я помню, как это делали для электроники GCSE! –

+1

Спасибо. Реализация SSE выполняется менее чем в половине случаев. Первоначально (перед публикацией этого сообщения) я взглянул на собранную C++-реализацию в надежде, что она поделится некоторыми предложениями. К сожалению, реализация C++ сильно затруднена всеми ветвями (и промашками кэш-памяти) - это не делает большую часть шаблона! Xor. И да, карты Карно - это бомба. – ZeroDefect

+0

Отлично! Хотя c вы используете u16, существует максимальное теоретическое ускорение скорости 8x для захвата. Легче сказать, чем сделать, как вы, наверное, узнали! –

 Смежные вопросы

  • Нет связанных вопросов^_^