Кажется, что _mm_shuffle_epi8 действительно является ключом к решению. Идея состоит в том, чтобы установить отдельные биты в соответствии со значениями входного вектора a. Эти биты распределены по (горизонтальное ИЛИ) байтам 128-битного регистра.
#include <stdio.h>
#include <immintrin.h>
/* gcc -O3 -Wall -mavx test4.c */
/* gcc -O3 -Wall -msse2 -mssse3 -msse4.1 test4.c */
int print_char128(__m128i * x);
int print_char128(__m128i * x){
unsigned char v_x[16];
_mm_storeu_si128((__m128i *)v_x,*x);
printf("%4u %4u %4u %4u | %4u %4u %4u %4u | %4u %4u %4u %4u | %4u %4u %4u %4u \n",
v_x[0], v_x[1], v_x[2], v_x[3], v_x[4], v_x[5], v_x[6], v_x[7],
v_x[8], v_x[9], v_x[10], v_x[11], v_x[12], v_x[13], v_x[14], v_x[15] );
return 0;
}
int main()
{
unsigned char a_v[] = {0, 0, 0, 10, 0, 0, 0, 2, 0, 0, 0, 0, 3, 1, 0, 0 };
/*unsigned char a_v[] = {13, 30, 0, 10, 0, 6, 0, 2, 0, 0, 7, 0, 3, 11, 0, 0 };*/
__m128i t0, t1, t2, t3;
__m128i a, r, msk0, msk1, msk0_1, zero, bin_ones, one_epi8;
/* set some constants */
unsigned char msk0_v[] ={1, 2, 4, 8, 16, 32, 64, 128, 0, 0, 0, 0, 0, 0, 0, 0};
msk0=_mm_loadu_si128((__m128i *)msk0_v);
msk1=_mm_shuffle_epi32(msk0,0b01001110);
msk0_1=_mm_blend_epi16(msk0,msk1,0b11110000);
zero=_mm_setzero_si128();
bin_ones=_mm_cmpeq_epi32(zero,zero);
one_epi8=_mm_sub_epi8(zero,bin_ones);
/* load indices */
a=_mm_loadu_si128((__m128i *)a_v);
/* start of 'SIMD magic' */
/* index a_i sets the a_i -th bit within a byte of t0 if 0<=a_i<8 */
/* or set (a_i-8)-th bit within a byte of t1 if 8<=a_i<16 */
t0=_mm_shuffle_epi8(msk0,a);
t1=_mm_shuffle_epi8(msk1,a);
/* horizontal OR of the bytes in t0 and t1: */
t2=_mm_blend_epi16(t0,t1,0b11110000);
t3=_mm_alignr_epi8(t1,t0,8);
t0=_mm_or_si128(t2,t3);
t1=_mm_shuffle_epi32(t0,0b10110001);
t0=_mm_or_si128(t0,t1);
t1=_mm_slli_si128(t0,2);
t0=_mm_or_si128(t0,t1);
t1=_mm_slli_si128(t0,1);
t0=_mm_or_si128(t0,t1);
t0=_mm_shuffle_epi32(t0,0b11110101); /* end of horizontal OR */
/* filter out the relevant bits */
t0=_mm_and_si128(t0,msk0_1);
t0=_mm_cmpeq_epi8(t0,zero);
r=_mm_andnot_si128(t0,one_epi8); /* the result is in r */
print_char128(&r);
return 0;
}
Это должно работать довольно быстро: помимо инструкций по установке констант и загрузке данных, это всего 15 инструкций SSEx. На современных процессорах все эти инструкции имеют задержку всего в 1 такт. (Взаимная) производительность еще меньше: 1/2 или 1/3 такта. Внутренний _mm_blend_epi16 — SSE4.1, некоторые другие — SSSE3.
01.06.2015
_mm_cmpeq
для генерации всех единиц. Любой вход равен самому себе. ЦП даже использует это преимущество, генерируя uop, который не зависит от предыдущего значения регистра, когда оба операнда одинаковы. (т. е. это распознается как инструкция, разрушающая зависимости.) 25.06.2015zero
reg в алгоритме. В противном случае вы могли бы получитьone_epi8
от_mm_sign_epi8(bin_ones, bin_ones)
. Я думал, что есть простая инструкция инвертирования вектора, но, возможно, это не так. 25.06.2015