2016-07-05 56 views
6

Ho una matrice allineata di numeri interi nella memoria che contiene gli indici I0, I1, I2, I3. Il mio obiettivo è di inserirli in un registro __m256i contenente I0, I0 + 1, I1, I1 + 1, I2, I2 + 1, I3, I3 + 1. La parte difficile è farli entrare nel registro a 256 bit come I0, I0 , I1, I1, I2, I2, I3, I3, dopo di che posso semplicemente aggiungere un registro contenente 0, 1, 0, 1, 0, 1, 0, 1.AVX2, Come caricare in modo efficiente quattro numeri interi agli indici pari di un registro a 256 bit e copia in indici dispari?

Ho trovato l'intrinseco, _mm256_castsi128_si256, che mi permette di caricare i 4 numeri interi nei 128 bit inferiori del registro a 256 bit, ma sto cercando di trovare i migliori elementi intrinseci da utilizzare da lì.

Qualsiasi aiuto sarebbe apprezzato. Ho accesso a tutte le versioni SSE, AVX e AVX2 e vorrei farlo usando solo intrinsics.

Edit:

Credo che questo funziona, ma io non sono come efficiente è ... in fase di testarlo.

// _mm128_load_si128: Loads 4 integer values into a temporary 128bit register. 
// _mm256_broadcastsi128_si256: Copies 4 integer values in the 128 bit register to the low and high 128 bits of the 256 bit register. 
__m256i tmpStuff = _mm256_broadcastsi128_si256 ((_mm_load_si128((__m128i*) indicesArray))); 

// _mm256_unpacklo_epi32: Interleaves the integer values of source0 and source1. 
__m256i indices = _mm256_unpacklo_epi32(tmpStuff, tmpStuff); 

__m256i regToAdd = _mm256_set_epi32 (0, 1, 0, 1, 0, 1, 0, 1); 
indices = _mm256_add_epi32(indices, regToAdd); 

Edit2: Il codice di cui sopra non funziona perché _mm256_unpacklo_epi32 non si comporta il modo in cui ho pensato. Il codice sopra darà come risultato I0, I0 + 1, I1, I1 + 1, I0, I0 + 1, I1, I1 + 1.

Edit3: Il seguente codice funziona, anche se ancora non sono sicuro se è il più efficiente:

__m256i tmpStuff = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*) indicesArray)); 
__m256i mask = _mm256_set_epi32 (3, 3, 2, 2, 1, 1, 0, 0); 
__m256i indices= _mm256_permutevar8x32_epi32(tmpStuff, mask); 
__m256i regToAdd = _mm256_set_epi32 (1, 0, 1, 0, 1, 0, 1, 0); // Set in reverse order. 
indices= _mm256_add_epi32(indices, regToAdd); 

risposta

6

tua versione 2 è così efficiente come è possibile essere, a meno che non mi manca un modo quello potrebbe piegare lo shuffle in un carico di 128b. Ciò potrebbe aiutare leggermente per il throughput di uop del dominio fused, ma non per il dominio non utilizzato.

1 carico (vmovdqa), 1 shuffle (vpermd, alias _mm256_permutevar8x32_epi32) e 1 add (vpaddd) è abbastanza leggero. A meno che non si esegua un carico non allineato di 256b con la divisione tra gli elementi 1 e 2, è necessario un qualche tipo di passaggio casuale per ottenere i 2 elementi superiori nella 128b superiore.

Poiché è possibile utilizzare AVX2, la soluzione è ottima se il caricamento di una maschera shuffle per vpermd non è un problema. (registra mancanza di pressione/cache).


Un'alternativa che evita un vettore costante riordino-maschera, ma è peggio altrimenti:

vpmovzxdq è un'altra opzione per ottenere la parte superiore due elementi nella corsia 128bit superiore.

vpmovzxdq ymm0, [src] 
vpshufd ymm1, ymm0, _MM_SHUFFLE(2,2, 0,0) ; duplicate elements 
vpaddd  ... 

Oppure, forse un throughput più elevato se la porta shuffle è un collo di bottiglia per l'intero ciclo. (Ancora peggio rispetto alla versione vpermd nella domanda, però.)

vpmovzxdq ymm0, [src] 
vpsrlq  ymm1, ymm0,32  ; left shift by 32 
vpaddd  ...     ; ymm1 +=1 in odd elements only 
vpor  ...     ; OR the incremented odd elements with the original even elements 

spostamento e OR sostituire il riordino in corsie.