2016-02-28 2 views
7

Если значение регистра SSE/AVX таково, что все его байты равны 0 или 1, есть ли способ эффективно получить индексы всех ненулевых элементов?Индексы ненулевых байтов регистра SSE/AVX

Например, если значение xmm = | r0 = 0 | r1 = 1 | r2 = 0 | r3 = 1 | r4 = 0 | r5 = 1 | r6 = 0 | ... | r14 = 0 | r15 = 1 | результат должен быть чем-то вроде (1, 3, 5, ..., 15). Результат должен быть помещен в другую переменную _m128i или char [16].

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

Мне очень интересно, есть ли инструкция для этого или, желательно, C/C++-встроенного. В любом наборе инструкций SSE или AVX.

EDIT 1:

Это правильно было observed by @zx485, что первоначальный вопрос был недостаточно ясен. Я искал любое «последовательное» решение.

Пример 0 1 0 1 0 1 0 1... выше должно привести одно из следующих действий:

  • Если предположить, что индексы начинаются с 1, то 0 будет терминации байт, и результат может быть
  • Если предположить, что отрицательный байт терминации байт результата может быть

001 003 005 007 009 011 013 015 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF

  • Все , который дает в виде последовательных байтов, которые мы можем интерпретировать как индексы ненулевых элементов в исходном значении

EDIT 2:

Действительно, как @harold и @Peter Cordes свидетельствуют в комментарии к первоначальному сообщению, одно из возможных решений состоит в создании маски первой (например, с pmovmskb) и проверьте там ненулевые индексы. Но это приведет к циклу.

+4

Вы можете сделать это с помощью «pmovmskb» и гигантского лута (но это не обязательно очень быстро). Кстати, кем вы хотите быть на дорожках, где нет индекса? Скажем, 0xFF? – harold

+2

Вы действительно просто хотите перебрать позиции, где был ненулевой элемент? Потому что вы можете сделать это с помощью 'pcmpeqb' против вектора all-zero (например, zx485 указывает), но затем используйте' pmovmskb'. Таким образом, вы превращаете свой вектор 0/1 в инвертированное растровое изображение в целочисленном регистре (1, где элемент равен 0). Вы можете зацикливать нули в растровом изображении. Может быть, наиболее легко, инвертируя его, и используя 'bsf' или' tzcnt', чтобы перебрать установленные биты. Есть инструкция BMI1 для очистки младшего бита набора, или вы можете сделать это пару инструкций с регулярными 2-битными командами IIRC. –

+0

Спасибо @harold. Вы оба правы. Дело в том, что нельзя избежать дополнительного цикла, если маска доступна. Мне было интересно, есть ли способ сделать это без цикла. Я обновил свое первоначальное сообщение (см. Раздел ** РЕДАКТИРОВАНИЕ 2 **). – TruLa

ответ

4

Ваш вопрос был неясным в отношении аспекта, если вы хотите, чтобы массив результатов был «сжат». То, что я подразумеваю под «сжатым», заключается в том, что результат должен быть последовательным.Так, например, для 0 1 0 1 0 1 0 1..., есть две возможности:

непоследовательных:

XMM0: 000 001 000 003 000 005 000 007 000 009 000 011 000 013 000 015

Последовательная:

XMM0: 003 005 001 007 009 011 013 015 000 000 000 000 000 000 000 000

Одна из проблем последовательного подхода заключается в следующем: как вы определяете, есть ли индекс 0 или значение терминации?

Я предлагаю простое решение первого, непоследовательного подход, который должен быть достаточно быстро:

.data 
    ddqZeroToFifteen    db 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 
    ddqTestValue:     db 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1 
.code 
    movdqa xmm0, xmmword ptr [ddqTestValue] 
    pxor xmm1, xmm1        ; zero XMM1 
    pcmpeqb xmm0, xmm1       ; set to -1 for all matching 
    pandn xmm0, xmmword ptr [ddqZeroToFifteen] ; invert and apply indices 

Просто для полноты картины: второй, последовательный подход, не распространяются в этом ответе.

+0

Спасибо @ zx485, я обновил свое оригинальное сообщение (см. Раздел ** РЕДАКТИРОВАТЬ 1 **). – TruLa

2

Обновленный ответ: новое решение немного более эффективно.

Вы можете сделать это без цикла, используя инструкцию pext от Bit Manipulation Instruction Set 2, в сочетании с несколькими другими инструкциями SSE.

/* 
gcc -O3 -Wall -m64 -mavx2 -march=broadwell ind_nonz_avx.c 
*/ 

#include <stdio.h> 
#include <immintrin.h> 
#include <stdint.h> 

__m128i nonz_index(__m128i x){ 
    /* Set some constants that will (hopefully) be hoisted out of a loop after inlining. */ 
    uint64_t indx_const = 0xFEDCBA;      /* 16 4-bit integers, all possible indices from 0 o 15               */ 
    __m128i cntr   = _mm_set_epi8(64,60,56,52,48,44,40,36,32,28,24,20,16,12,8,4); 
    __m128i pshufbcnst = _mm_set_epi8(0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, 0x0E,0x0C,0x0A,0x08,0x06,0x04,0x02,0x00); 
    __m128i cnst0F  = _mm_set1_epi8(0x0F); 

    __m128i msk   = _mm_cmpeq_epi8(x,_mm_setzero_si128()); /* Generate 16x8 bit mask.                      */ 
      msk   = _mm_srli_epi64(msk,4);     /* Pack 16x8 bit mask to 16x4 bit mask.                   */ 
      msk   = _mm_shuffle_epi8(msk,pshufbcnst);   /* Pack 16x8 bit mask to 16x4 bit mask, continued.                */ 
    uint64_t msk64  = ~ _mm_cvtsi128_si64x(msk);     /* Move to general purpose register and invert 16x4 bit mask.              */ 

                     /* Compute the termination byte nonzmsk separately.                */ 
    int64_t nnz64  = _mm_popcnt_u64(msk64);     /* Count the nonzero bits in msk64.                    */ 
    __m128i nnz   = _mm_set1_epi8(nnz64);      /* May generate vmovd + vpbroadcastb if AVX2 is enabled.               */ 
    __m128i nonzmsk  = _mm_cmpgt_epi8(cntr,nnz);     /* nonzmsk is a mask of the form 0xFF, 0xFF, ..., 0xFF, 0, 0, ...,0 to mark the output positions without an index */ 

    uint64_t indx64  = _pext_u64(indx_const,msk64);    /* parallel bits extract. pext shuffles indx_const such that indx64 contains the nnz64 4-bit indices that we want.*/ 
    __m128i indx   = _mm_cvtsi64x_si128(indx64);    /* Use a few integer instructions to unpack 4-bit integers to 8-bit integers.          */ 
    __m128i indx_024  = indx;          /* Even indices.                         */ 
    __m128i indx_135  = _mm_srli_epi64(indx,4);     /* Odd indices.                         */ 
      indx   = _mm_unpacklo_epi8(indx_024,indx_135);  /* Merge odd and even indices.                     */ 
      indx   = _mm_and_si128(indx,cnst0F);    /* Mask out the high bits 4,5,6,7 of every byte.                 */ 

      return _mm_or_si128(indx,nonzmsk);      /* Merge indx with nonzmsk .                      */ 
} 


int main(){ 
    int i; 
    char w[16],xa[16]; 
    __m128i x; 

    /* Example with bytes 15, 12, 7, 5, 4, 3, 2, 1, 0 set. */ 
    x = _mm_set_epi8(1,0,0,1, 0,0,0,0, 1,0,1,1, 1,1,1,1); 

    /* Other examples. */ 
    /* 
    x = _mm_set_epi8(1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1); 
    x = _mm_set_epi8(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0); 
    x = _mm_set_epi8(1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0); 
    x = _mm_set_epi8(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1); 
    */ 
    __m128i indices = nonz_index(x); 
    _mm_storeu_si128((__m128i *)w,indices); 
    _mm_storeu_si128((__m128i *)xa,x); 

    printf("counter 15..0 ");for (i=15;i>-1;i--) printf(" %2d ",i);  printf("\n\n"); 
    printf("example xmm: ");for (i=15;i>-1;i--) printf(" %2d ",xa[i]); printf("\n"); 
    printf("result in dec ");for (i=15;i>-1;i--) printf(" %2hhd ",w[i]); printf("\n"); 
    printf("result in hex ");for (i=15;i>-1;i--) printf(" %2hhX ",w[i]); printf("\n"); 

    return 0; 
} 

Требуется около пяти инструкций, чтобы получить 0xFF (байт окончания) в нежелательных позициях. Обратите внимание, что функция nonz_index, которая возвращает индексы и только положение конечного байта, без фактического , вставляя байт завершения (окончания), будет намного дешевле вычислять и может быть подходящим для конкретного приложения. Положение первого байт завершения составляет nnz64>>2.

Результат:

$ ./a.out 
counter 15..0 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 

example xmm: 1 0 0 1 0 0 0 0 1 0 1 1 1 1 1 1 
result in dec -1 -1 -1 -1 -1 -1 -1 15 12 7 5 4 3 2 1 0 
result in hex FF FF FF FF FF FF FF F C 7 5 4 3 2 1 0 

pext инструкция поддерживается на процессорах Intel Haswell или более поздней версии.

Смежные вопросы