Горизонтальные минимальные и максимальные значения с использованием SSE

У меня есть функция, использующая SSE, чтобы сделать много вещей, и профайлер показывает мне, что часть кода, которую я использую для вычисления горизонтального минимума и максимума, потребляет большую часть времени.

Я использовал следующую реализацию для минимума, например:

static inline int16_t hMin(__m128i buffer) {
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m1));
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m2));
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m3));
    buffer = _mm_min_epi8(buffer, _mm_shuffle_epi8(buffer, m4));
    return ((int8_t*) ((void *) &buffer))[0];
}

Мне нужно вычислить минимум и максимум 16 1-байтовых целых чисел, как вы видите.

Любые хорошие предложения высоко ценятся:)

Спасибо

Ответ 1

Я предлагаю два изменения:

  • Замените ((int8_t*) ((void *) &buffer))[0] на _mm_cvtsi128_si32.
  • Замените _mm_shuffle_epi8 на _mm_shuffle_epi32/_mm_shufflelo_epi16, которые имеют более низкую задержку для последних процессоров AMD и Intel Atom и будут экономить ваши операции загрузки памяти:

    static inline int16_t hMin(__m128i buffer)
    {
        buffer = _mm_min_epi8(buffer, _mm_shuffle_epi32(buffer, _MM_SHUFFLE(3, 2, 3, 2)));
        buffer = _mm_min_epi8(buffer, _mm_shuffle_epi32(buffer, _MM_SHUFFLE(1, 1, 1, 1)));
        buffer = _mm_min_epi8(buffer, _mm_shufflelo_epi16(buffer, _MM_SHUFFLE(1, 1, 1, 1)));
        buffer = _mm_min_epi8(buffer, _mm_srli_epi16(buffer, 8));
        return (int8_t)_mm_cvtsi128_si32(buffer);
    }
    

Ответ 2

SSE 4.1 имеет инструкцию, которая делает почти то, что вы хотите. Его имя PHMINPOSUW, C/С++ intrinsic _mm_minpos_epu16. Он ограничен 16-разрядными значениями без знака и не может дать максимум, но эти проблемы могут быть легко решены.

  • Если вам нужно найти минимум неотрицательных байтов, ничего не делайте. Если байты могут быть отрицательными, добавьте 128 к каждому. Если вам нужен максимум, вычтите каждый из 127.
  • Используйте либо _mm_srli_pi16, либо _mm_shuffle_epi8, а затем _mm_min_epu8, чтобы получить 8 парных минимальных значений в четных байтах и ​​нулях в нечетных байтах некоторого регистра XMM. (Эти нули производятся командой shift/shuffle и должны оставаться на своих местах после _mm_min_epu8).
  • Используйте _mm_minpos_epu16, чтобы найти минимум среди этих значений.
  • Извлеките полученное минимальное значение с помощью _mm_cvtsi128_si32.
  • Отмените действие шага 1, чтобы получить исходное значение байта.

Вот пример, который возвращает максимум 16 подписанных байтов:

static inline int16_t hMax(__m128i buffer)
{
    __m128i tmp1 = _mm_sub_epi8(_mm_set1_epi8(127), buffer);
    __m128i tmp2 = _mm_min_epu8(tmp1, _mm_srli_epi16(tmp1, 8));
    __m128i tmp3 = _mm_minpos_epu16(tmp2);
    return (int8_t)(127 - _mm_cvtsi128_si32(tmp3));
}