Получение минимального короткого значения в векторе __m128i с SSE?

Этот вопрос кажется похожим на Получение максимального значения в векторе __m128i с SSE? но с шортами и минимумом вместо целого + максимум. Вот что я придумал:

typedef short int weight;

weight horizontal_min_Vec4i(__m128i x) {
    __m128i max1 = _mm_shufflehi_epi16(x, _MM_SHUFFLE(0, 0, 3, 2));
    __m128i max1b = _mm_shufflelo_epi16(x, _MM_SHUFFLE(0, 0, 3, 2));
    __m128i max2 = _mm_min_epi16(max1, max1b);
    //max2 = _mm_min_epi16(max2, x);
    max1 = _mm_shufflehi_epi16(max2, _MM_SHUFFLE(0, 0, 0, 1));
    max1b = _mm_shufflelo_epi16(max2, _MM_SHUFFLE(0, 0, 0, 1));
    __m128i max3 = _mm_min_epi16(max1, max1b);
    max2 = _mm_min_epi16(max2, max3);
    return min(_mm_extract_epi16(max2, 0), _mm_extract_epi16(max2, 4));
}

Функция в основном делает то же самое, что и ответ в /questions/36717993/poluchit-maksimalnoe-znachenie-v-vektore-m128i-s-sse/36718013#36718013 для верхней и нижней частей x. Итак, я знаю, что минимальное значение находится в позиции 0 или 4 переменной __m128i max2. Хотя это намного быстрее, чем функция без SIMD horizontal_min_Vec4i_Plain(__m128i x) показано ниже, я боюсь, что узким местом является _mm_extract_epi16 operation на последней строке. Есть ли лучший способ добиться этого, для лучшего ускорения? Я использую Haswell, поэтому у меня есть доступ к последним расширениям SSE.

weight horizontal_min_Vec4i_Plain(__m128i x) {
    weight result[8] __attribute__((aligned(16)));
    _mm_store_si128((__m128i *) result, x);
    weight myMin = result[0];
    for (int l = 1; l < 8; l++) {
        if (myMin > result[l]) {
            myMin = result[l];
        }
    }
    return myMin;
}

1 ответ

Решение

Сравнение со знаком и без знака почти одинаково, за исключением того, что диапазон с установленным верхним битом рассматривается как больший, чем диапазон с верхним битом, не установленным в сравнениях без знака, и как меньший в сравнениях со знаком. Это означает, что сравнения со знаком и без знака могут быть преобразованы друг в друга по следующим правилам:

x <s y = (x ^ signbit) <u (y ^ signbit)
x <u y = (x ^ signbit) <s (y ^ signbit)

Эта собственность переходит непосредственно к min а также max, так:

min_s(x, y) = min_u(x ^ signbit, y ^ signbit) ^ signbit

И тогда мы можем использовать _mm_minpos_epu16 обрабатывать горизонтальный минимум, чтобы получить, в целом, что-то вроде

__m128i xs = _mm_xor_si128(x, _mm_set1_epi16(0x8000));
return _mm_extract_epi16(_mm_minpos_epu16(xs), 0) - 0x8000;

- 0x8000 является ^ 0x8000 и знак-расширение (extract ноль-расширяет) свернул в одну.

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