Встроенные функции SSE2 — сравнение 2 __m128i, содержащих по 4 int32 в каждом, чтобы увидеть, сколько из них равны

Я впервые погружаюсь во встроенные функции SSE2 и не знаю, как это сделать.

Я хочу сравнить 4 int32 с 4 другими int32 и подсчитать, сколько из них равны. Итак, я прочитал свои первые 4 int32, установил их в __m128i, сделал то же самое для второго набора и использовал _mm_cmpeq_epi32 для сравнения.

Это должно привести к __m128i, содержащему 4 int32, каждый из которых либо 0xffffffff, либо 0, в зависимости от того, равны ли целые числа.

Но я понятия не имею, как из полученного __m128i получить количество, указывающее, сколько на самом деле было равно.

Может кто-то указать мне верное направление ?

Код, насколько я его собираю:

        int* source = blah;
        int* reference = otherblah;

        // Load the 4 source int32's (they are actually 4 int32s apart)
        __m128i first_4_int32s = _mm_set_epi32(*(source + 12), *(source + 8), *(source + 4), *(source));

        // Load the 4 source int32's (also actually 4 int32s apart)
        __m128i second_4_int32s = _mm_set_epi32(*(reference + 12), *(reference + 8), *(reference + 4), *(reference));

        // Compare the int32's
        __m128i result = _mm_cmpeq_epi32(first_4_int32s, second_4_int32s);

        // Perform magic here that counts whether 0, 1, 2, 3 or all 4 ints were equal ?!?!

person Pygmy    schedule 08.07.2014    source источник
comment
Обратите внимание, что выполнение собранных загрузок, подобных этому, является рецептом для низкой производительности — вы можете также придерживаться скалярного кода, если не можете использовать непрерывные данные.   -  person Paul R    schedule 08.07.2014
comment
Да, то, что вы делаете здесь, вероятно, намного медленнее, чем последовательно. Поэтому не тратьте время на его векторизацию, если вы не можете изменить свой шаблон доступа к памяти.   -  person Mysticial    schedule 08.07.2014
comment
Вы делаете это только для 4 значений или для целой группы, но только для 4 за раз? Сообщите мне, и я буду рад написать решение. Помимо этого, вот несколько моментов. Если это действительно всего 4 значения, рассмотрите _mm_packs_epi32, затем _mm_packs_epi16, затем _mm_movemask_epi8, затем справочную таблицу, но скалярная версия, вероятно, быстрее. Несмотря на это, вы НЕ должны использовать _mm_set_epi32 таким образом, вместо этого вы должны использовать _mm_load_ps или _mm_loadu_ps для загрузки всех 4 значений вместе. Теперь, если вы собираетесь сделать это для целой группы 4-кортежей, вы можете просто сохранить 4 суммы в __m128i.   -  person Apriori    schedule 11.07.2014
comment
Затем в конце вы можете суммировать 4 компонента в одно значение. Это можно сделать с помощью сдвигов и горизонтальных добавлений или вычитания отдельных компонентов. В любом случае, это делается только один раз в конце. При суммировании всего по отдельности у вас есть два возможных значения после сравнения. 0x00000000 или 0xFFFFFFFF. Имейте в виду, что 0xFFFFFFFF также -1. Итак, что вы можете сделать, так это вычесть значение сравнения из текущей суммы, используя _mm_sub_epi32. Таким образом, текущий минус 0 остается текущим. И текущий минус -1 становится текущим плюс 1 (вычитание отрицательного добавляет положительное).   -  person Apriori    schedule 11.07.2014


Ответы (1)


Вы можете И сравнить результат с вектором единиц, чтобы создать вектор нулей и единиц. Затем используйте горизонтальную операцию добавления, чтобы подсчитать единицы. Вот некоторые возможности.

#include "stdio.h"
#include "stdint.h"
#include "intrin.h"

//----------------------------------------------------------------------------
// non-SSE method (reference for result check)
static int method0 (__m128i value)
    {
    int index, total = 0;
    uint32_t *buffer = (void *) &value;

    for (index = 0; index < 4; index++)
        total += buffer [index] == 0xFFFFFFFF;
    return total;
    }

//----------------------------------------------------------------------------
//
// horizontalAddBytes - return integer total of all 16 bytes in xmm argument
//
static int horizontalAddBytes (__m128i byteArray)
   {
   __m128i total;
   const __m128i zero = _mm_setzero_si128 ();

   total = _mm_sad_epu8 (byteArray, zero);
   return _mm_cvtsi128_si64 (_mm_add_epi32 (total, _mm_shuffle_epi32 (total, 0xAA)));
   }

//----------------------------------------------------------------------------
// requires SSE2
static int method1 (__m128i value)
    {
    return horizontalAddBytes (_mm_srli_epi32 (value, 31));
    }

//----------------------------------------------------------------------------
// requires SSE3
static int method2 (__m128i value)
    {
    __m128 count;
    const __m128 mask = _mm_set1_ps (1);
    count = _mm_and_ps (_mm_castsi128_ps (value), mask);
    count = _mm_hadd_ps (count, count);
    count = _mm_hadd_ps (count, count);
    return _mm_cvtss_si32 (count);
    }

//----------------------------------------------------------------------------
// requires SSSE3
static int method3 (__m128i value)
    {
    __m128i count;
    count = _mm_srli_epi32 (value, 31);
    count = _mm_hadd_epi32 (count, count);
    count = _mm_hadd_epi32 (count, count);
    return _mm_cvtsi128_si32 (count);
    }

//----------------------------------------------------------------------------

static void createTestData (uint32_t *data, int mask)
    {
    int index;
    for (index = 0; index < 4; index++)
        data [index * 4] = (mask & (1 << index)) != 0;
    }

//----------------------------------------------------------------------------

int main (void)
    {
    int index1, index2, expected, result1, result2, result3;
    uint32_t source [16];
    uint32_t reference [16];

    for (index1 = 0; index1 < 16; index1++)
        for (index2 = 0; index2 < 16; index2++)
            {
            __m128i first_4_int32s, second_4_int32s, result;
            createTestData (source, index1);
            createTestData (reference, index2);

            // Load the 4 source int32's (they are actually 4 int32s apart)
            first_4_int32s = _mm_set_epi32(*(source + 12), *(source + 8), *(source + 4), *(source));

            // Load the 4 source int32's (also actually 4 int32s apart)
            second_4_int32s = _mm_set_epi32(*(reference + 12), *(reference + 8), *(reference + 4), *(reference));

            // Compare the int32's
            result = _mm_cmpeq_epi32(first_4_int32s, second_4_int32s);

            expected = method0 (result);
            result1 = method1 (result);
            result2 = method2 (result);
            result3 = method3 (result);
            if (result1 != expected) printf ("method1, index %d,%d expected %d, actual %d\n", index1, index2, expected, result1);
            if (result2 != expected) printf ("method2, index %d,%d expected %d, actual %d\n", index1, index2, expected, result2);
            if (result3 != expected) printf ("method3, index %d,%d expected %d, actual %d\n", index1, index2, expected, result3);
            }

    return 0;
    }

//----------------------------------------------------------------------------
person Community    schedule 09.07.2014
comment
Не нужно маскироваться. Просто выполните _mm_srl_epi32(value, 31), чтобы преобразовать 0xFFFFFFFF в 0x00000001. - person Raymond Chen; 12.07.2014
comment
@Raymond: Спасибо, ваше предложение, безусловно, проще и эффективнее. Я обновил код. - person ; 13.07.2014