Как оптимизировать цикл?

У меня есть следующая функция узкого места.

typedef unsigned char byte;
void CompareArrays(const byte * p1Start, const byte * p1End, const byte * p2, byte * p3)
{
     const byte b1 = 128-30;
     const byte b2 = 128+30;
     for (const byte * p1 = p1Start; p1 != p1End; ++p1, ++p2, ++p3) {
        *p3 = (*p1 < *p2 ) ? b1 : b2;
    }
}

Я хочу заменить C++ код с внутренними функциями SSE2. я пытался _mm_cmpgt_epi8 но он использовал подписанное сравнение. Мне нужно без знака сравнить.

Есть ли хитрость (SSE, SSE2, SSSE3) для решения моей проблемы?

Примечание: я не хочу использовать многопоточность в этом случае.

6 ответов

Решение

Вместо смещения ваших значений со знаком, чтобы сделать их без знака, чуть более эффективным способом было бы сделать следующее:

  • использование _mm_min_epu8 чтобы получить мин без знака p1, p2
  • сравните этот минимум на равенство с p2, используя _mm_cmpeq_epi8
  • результирующая маска теперь будет 0x00 для элементов, где p1 = p2
  • Теперь вы можете использовать эту маску с _mm_or_si128 а также _mm_andc_si128 выбрать соответствующие значения b1/b2

Обратите внимание, что это всего 4 инструкции по сравнению с 5 с использованием подхода сравнения "смещение + знак".

Да, это можно сделать в SIMD, но для создания маски потребуется несколько шагов.

Руслик понял это правильно, я думаю. Вы хотите xor каждого компонента с 0x80, чтобы перевернуть смысл сравнения со знаком и без знака. _mm_xor_si128 (PXOR) вам это нужно - вам нужно создать маску в виде статического массива символов перед загрузкой в ​​SIMD-регистр. Затем _mm_cmpgt_epi8 получает маску, и вы можете использовать побитовое И (например, _mm_and_si128) выполнить замаскированное движение.

Вы можете вычесть 127 из ваших чисел, а затем использовать _mm_cmpgt_epi8

Да, SSE здесь работать не будет. Вы можете улучшить производительность этого кода на многоядерном компьютере, используя OpenMP:

void CompareArrays (const byte * p1Start, const byte * p1End, const byte * p2, byte * p3)
{
     константный байт b1 = 128-30;
     константный байт b2 = 128 + 30;

     int n = p1End - p1Start;
     #pragma omp параллельный для
     для (int i = 0; i 
                                        
                                    
                                

К сожалению, многие из приведенных выше ответов неверны. Давайте предположим, что 3-битное слово:

без знака: 4 5 6 7 0 1 2 3 == подписано: -4 -3 -2 -1 0 1 2 3 (биты: 100 101 110 111 000 001 010 011)

Метод Пола Р. неверен. Предположим, мы хотим знать, если 3 > 2. min(3,2) == 2, что говорит о да, поэтому метод работает здесь. Теперь предположим, что мы хотим знать, если 7 > 2. Значение 7 равно -1 в представлении со знаком, поэтому min(-1,2) == -1, что неверно указывает на то, что 7 не больше 2 без знака.

Метод Андрея тоже неверен. Предположим, что мы хотим знать, если 7 > 2, или a = 7, и b = 2. Значение 7 равно -1 в знаковом представлении, поэтому первый член (a > b) не выполняется, и метод предполагает, что 7 не больше чем 2.

Однако метод BJobnh, исправленный Алексеем, является правильным. Просто вычтите 2^(n-1) из значений, где n - количество бит. В этом случае мы бы вычли 4, чтобы получить новые соответствующие значения:

старая подпись: -4 -3 -2 -1 0 1 2 3 => новая подпись: 0 1 2 3 -4 -3 -2 -1 == новая неподписанная 0 1 2 3 4 5 6 7.

Другими словами, unsigned_greater_than(a,b) эквивалентен sign_greater_than(a - 2^(n-1), b - 2^(n-1)).

Используй pcmpeqb и будь Силой с тобой.

Другие вопросы по тегам