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