Перевод SSE в Neon: как упаковать и затем извлечь 32-битный результат



Я должен перевести следующие инструкции из SSE в Neon



 uint32_t a = _mm_cvtsi128_si32(_mm_shuffle_epi8(a,SHUFFLE_MASK) );


Где:



static const __m128i SHUFFLE_MASK = _mm_setr_epi8(3,  7,  11, 15, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1);


Поэтому в основном я должен взять 4,8, 12 и 16 байт из регистра и поместить его в uint32_t. Похоже на инструкцию по упаковке (в SSE я, кажется, помню, что использовал shuffle, потому что он сохраняет одну инструкцию по сравнению с упаковкой, этот пример показывает использование инструкций по упаковке).

Как эта операция переводится в Неон?
Должен ли я использовать упаковку инструкции?
Как же тогда извлечь 32 бита? (Есть ли что-нибудь эквивалентное _mm_cvtsi128_si32?)



Редактировать:

Для начала:, vgetq_lane_u32 следует разрешить заменить _mm_cvtsi128_si32
(но мне придется привести мой uint8x16_t к uint32x4_t)



uint32_t  vgetq_lane_u32(uint32x4_t vec, __constrange(0,3) int lane);


Или непосредственно хранить переулок vst1q_lane_u32



void  vst1q_lane_u32(__transfersize(1) uint32_t * ptr, uint32x4_t val, __constrange(0,3) int lane); // VST1.32 {d0[0]}, [r0]
591   2  

2 ответов:

Я нашел это превосходное руководство. Я работаю над этим, кажется, что моя операция может быть выполнена с помощью одной инструкции VTBL (посмотрите таблицу), но я буду реализовывать ее с помощью 2 операций деинтерлейвинга, потому что на данный момент это выглядит проще.

uint8x8x2_t   vuzp_u8(uint8x8_t a, uint8x8_t b);

Итак, что-то вроде:

uint8x16_t a;
uint8_t* out;
[...]

//a = 138 0 0 0 140 0 0 0 146 0 0 0 147 0 0 0

a = vuzp_u8(vget_low_u8(a), vget_high_u8(a) );
//a = 138 0 140 0 146 0 147 0 0 0 0 0 0 0 0 0

a = vuzp_u8(vget_low_u8(a), vget_high_u8(a) );
//a = 138 140 146 147 0 0 0 0 0 0 0 0 0 0 0 0

vst1q_lane_u32(out,a,0);

Последний не дает предупреждения, используя __attribute__((optimize("lax-vector-conversions")))

Но, из-за преобразования данных, 2 назначения не возможны. Один обходной путь выглядит так (редактировать: Это нарушает строгое сглаживание правила! Компилятор может предположить, что a не изменяется при назначении адреса d.):

uint8x8x2_t* d = (uint8x8x2_t*) &a;
*d = vuzp_u8(vget_low_u8(a), vget_high_u8(a) );
*d = vuzp_u8(vget_low_u8(a), vget_high_u8(a) );
vst1q_lane_u32(out,a,0);

Я реализовал более общий обходной путь с помощью гибкого типа данных:

NeonVectorType<uint8x16_t> a; //a can be used as a uint8x16_t, uint8x8x2_t, uint32x4_t, etc.
a = vuzp_u8(vget_low_u8(a), vget_high_u8(a) );
a = vuzp_u8(vget_low_u8(a), vget_high_u8(a) );
vst1q_lane_u32(out,a,0);

Редактировать:

Вот версия с shuffle mask / look up table. Это действительно делает мою внутреннюю петлю немного быстрее. Опять же, я использовал тип данных, описанный здесь .

static const uint8x8_t MASK = {0x00,0x04,0x08,0x0C,0xff,0xff,0xff,0xff};
NeonVectorType<uint8x16_t> a; //a can be used as a uint8x16_t, uint8x8x2_t, uint32x4_t, etc.
NeonVectorType<uint8x8_t> res; //res can be used as uint8x8_t, uint32x2_t, etc.
[...]
res = vtbl2_u8(a, MASK);
vst1_lane_u32(out,res,0);

Я бы написал это так:

uint32_t extract (uint8x16_t x)
{
  uint8x8x2_t a = vuzp_u8 (vget_low_u8 (x), vget_high_u8 (x));
  uint8x8x2_t b = vuzp_u8 (a.val[0], a.val[1]);
  return vget_lane_u32 (vreinterpret_u32_u8 (b.val[0]), 0);
}

Который в последней версии GCC компилируется в:

extract:
    vuzp.8  d0, d1
    vuzp.8  d0, d1
    vmov.32 r0, d0[0]
    bx  lr

Comments

    Ничего не найдено.