SSE2 - сравнение целых чисел без знака

Я заинтересован в определении переполнения значений при добавлении 8-битных целых без знака и насыщении результата до 0xFF:

__m128i m1 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m2 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);

__m128i m3 = _mm_adds_epu8(m1, m2);

Мне было бы интересно выполнить сравнение для менее чем на этих целых без знака, аналогично _mm_cmplt_epi8 для подписи:

__m128i mask = _mm_cmplt_epi8 (m3, m1);
m1 = _mm_or_si128(m3, mask);

Если был доступен эквивалент "epu8", mask будет иметь 0xFF где m3[i] < m1[i] (Переполнение!), 0x00 otherwiseи мы сможем насытить m1 используя "или", так m1 будет содержать результат сложения, где он действителен, и 0xFF где это переполнилось.

Проблема в том, _mm_cmplt_epi8 выполняет сравнение со знаком, например, если m1[i] = 0x70 а также m2[i] = 0x10, затем m3[i] = 0x80 а также mask[i] = 0xFFчто, очевидно, не то, что мне нужно.

Использование VS2012.

Я был бы признателен за другой подход для выполнения этого. Спасибо!

4 ответа

Решение

Одним из способов реализации сравнений для 8-битных векторов без знака является использование _mm_max_epu8, который возвращает максимум 8-битных элементов без знака. Вы можете сравнить для равенства (без знака) максимальное значение двух элементов с одним из исходных элементов и затем вернуть соответствующий результат. Это переводит к 2 инструкциям для >= или же <=и 3 инструкции для > или же <,

Пример кода:

#include <stdio.h>
#include <emmintrin.h>    // SSE2

#define _mm_cmpge_epu8(a, b) \
        _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)

#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)

#define _mm_cmpgt_epu8(a, b) \
        _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))

#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)

int main(void)
{
    __m128i va = _mm_setr_epi8(0,   0,   1,   1,   1, 127, 127, 127, 128, 128, 128, 254, 254, 254, 255, 255);
    __m128i vb = _mm_setr_epi8(0, 255,   0,   1, 255,   0, 127, 255,   0, 128, 255,   0, 254, 255,   0, 255);

    __m128i v_ge = _mm_cmpge_epu8(va, vb);
    __m128i v_le = _mm_cmple_epu8(va, vb);
    __m128i v_gt = _mm_cmpgt_epu8(va, vb);
    __m128i v_lt = _mm_cmplt_epu8(va, vb);

    printf("va   = %4vhhu\n", va);
    printf("vb   = %4vhhu\n", vb);
    printf("v_ge = %4vhhu\n", v_ge);
    printf("v_le = %4vhhu\n", v_le);
    printf("v_gt = %4vhhu\n", v_gt);
    printf("v_lt = %4vhhu\n", v_lt);

    return 0;
}

Скомпилируйте и запустите:

$ gcc -Wall _mm_cmplt_epu8.c && ./a.out 
va   =    0    0    1    1    1  127  127  127  128  128  128  254  254  254  255  255
vb   =    0  255    0    1  255    0  127  255    0  128  255    0  254  255    0  255
v_ge =  255    0  255  255    0  255  255    0  255  255    0  255  255    0  255  255
v_le =  255  255    0  255  255    0  255  255    0  255  255    0  255  255    0  255
v_gt =    0    0  255    0    0  255    0    0  255    0    0  255    0    0  255    0
v_lt =    0  255    0    0  255    0    0  255    0    0  255    0    0  255    0    0

Другие ответы заставили меня задуматься о более простом способе ответить на конкретный вопрос более прямо:

Чтобы просто обнаружить зажим, сделайте насыщающие и ненасыщенные дополнения и сравните результаты.

__m128i m1 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);
__m128i m2 = _mm_loadu_si128(/* 16 8-bit unsigned integers */);

__m128i m1m2_sat = _mm_adds_epu8(m1, m2);
__m128i m1m2_wrap = _mm_add_epi8(m1, m2);
__m128i non_clipped = _mm_cmpeq_epi8(m1m2_sat, m1m2_wrap);

Так что это всего лишь две инструкции за addsи один из них может работать параллельно с adds, Итак non_clipped маска готова через один цикл после результата сложения. (Потенциально 3 инструкции (дополнительное movdqa) без неразрушающего вектора операций с 3 операндами AVX.)

Если результат ненасыщенного добавления равен 0xFF, он будет соответствовать результату насыщающего добавления и будет обнаружен как не ограничивающий. Вот почему это отличается от простой проверки вывода насыщающего добавления для байтов 0xFF.

Другой способ сравнить неподписанные байты: добавить 0x80 и сравни их как подписанные.

__m128i _mm_cmplt_epu8(__m128i a, __m128i b) {
    __m128i as = _mm_add_epi8(a, _mm_set1_epi8((char)0x80));
    __m128i bs = _mm_add_epi8(b, _mm_set1_epi8((char)0x80));
    return _mm_cmplt_epi8(as, bs);
}

Я не думаю, что это очень эффективно, но это работает, и это может быть полезно в некоторых случаях. Кроме того, вы можете использовать xor вместо сложения, если хотите. В некоторых случаях вы можете даже выполнить двунаправленную проверку диапазона сразу, то есть сравнить значение с нижней и верхней границами. Для этого выровняйте нижнюю границу с 0x80аналогично тому, что делает этот ответ.

Есть реализация сравнения 8-битного целого без знака:

    inline __m128i NotEqual8u(__m128i a, __m128i b)
    {
        return _mm_andnot_si128(_mm_cmpeq_epi8(a, b), _mm_set1_epi8(-1));
    }

    inline __m128i Greater8u(__m128i a, __m128i b)
    {
        return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_min_epu8(a, b), a), _mm_set1_epi8(-1));
    }

    inline __m128i GreaterOrEqual8u(__m128i a, __m128i b)
    {
        return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a);
    }

    inline __m128i Lesser8u(__m128i a, __m128i b)
    {
        return _mm_andnot_si128(_mm_cmpeq_epi8(_mm_max_epu8(a, b), a), _mm_set1_epi8(-1));
    }

    inline __m128i LesserOrEqual8u(__m128i a, __m128i b)
    {
        return _mm_cmpeq_epi8(_mm_min_epu8(a, b), a);
    }
Другие вопросы по тегам