2015-12-27 23 views
5

Ho bisogno di spostare una variabile __m128i, (ad esempio v), di m bit, in modo tale che i bit si spostino attraverso tutte le variabili (quindi la variabile risultante rappresenta v * 2^m). Qual è il modo migliore per farlo ?!Il modo migliore per spostare un __m128i?

Nota che _mm_slli_epi64 turni v0 e V1 separatamente:

r0 := v0 << count 
r1 := v1 << count 

così gli ultimi pezzi di v0 perso, ma voglio spostare i bit da r1.

Edit: Io alla ricerca di un codice, più veloce di questo (m < 64):

r0 = v0 << m; 
r1 = v0 >> (64-m); 
r1 ^= v1 << m; 
r2 = v1 >> (64-m); 
+1

Se 'M' sembra essere un multiplo di 8 bit e si dispone SSSE3, siete fortunati:' palignr'. In caso contrario, diventa brutto velocemente e davvero, davvero bisogno di fare turni, AND, shuffles e OR. –

+1

Vedere http://stackoverflow.com/questions/9980801/looking-for-sse-128-bit-shift-operation-for-non-immediate-shift-value –

+0

Si stanno elaborando flussi di bit o variabili aritmetiche (int galleggia, ecc.)? – bazza

risposta

1

In SSE4.A le istruzioni insrq e extrq possono essere utilizzati per spostare (e rotazione) attraverso __mm128i 1 -64 bit alla volta. A differenza delle controparti 8/16/32/64 bit pextrN/pinsrX, queste istruzioni selezionano o inseriscono m bit (tra 1 e 64) con qualsiasi offset di bit da 0 a 127. L'avvertenza è che la somma della lunghezza e dell'offset non deve superare 128.

+0

Vedere la risposta modificata. Non c'è p nell'istruzione giusta. –

+2

Il grande avvertimento sembra essere solo il suo AMD. –

3

Per i conteggi di spostamento costante in fase di compilazione, è possibile ottenere risultati abbastanza buoni. Altrimenti non proprio.

Questa è solo un'implementazione SSE del codice r0/r1 dalla tua domanda, poiché non esiste un altro modo ovvio per farlo. Gli spostamenti di conteggio variabile sono disponibili solo per spostamenti di bit all'interno di elementi vettoriali, non per spostamenti di byte dell'intero registro. Quindi portiamo i 64 bit bassi fino ai 64 alti e usiamo uno spostamento a numero variabile per metterli nel posto giusto.

// untested 
#include <immintrin.h> 

/* some compilers might choke on slli/srli with non-compile-time-constant args 
* gcc generates the xmm, imm8 form with constants, 
* and generates the xmm, xmm form with otherwise. (With movd to get the count in an xmm) 
*/ 

// doesn't optimize for the special-case where count%8 = 0 
// could maybe do that in gcc with if(__builtin_constant_p(count)) { if (!count%8) return ...; } 
__m128i mm_bitshift_left(__m128i x, unsigned count) 
{ 
    __m128i carry = _mm_bslli_si128(x, 8); // old compilers only have the confusingly named _mm_slli_si128 synonym 
    if (count >= 64) 
     return _mm_slli_epi64(carry, count-64); // the non-carry part is all zero, so return early 
    // else 
    carry = _mm_srli_epi64(carry, 64-count); // After bslli shifted left by 64b 

    x = _mm_slli_epi64(x, count); 
    return _mm_or_si128(x, carry); 
} 

__m128i mm_bitshift_left_3(__m128i x) { // by a specific constant, to see inlined constant version 
    return mm_bitshift_left(x, 3); 
} 
// by a specific constant, to see inlined constant version 
__m128i mm_bitshift_left_100(__m128i x) { return mm_bitshift_left(x, 100); } 

Ho pensato che questo sarebbe stato meno conveniente di quello che si è rivelato essere. _mm_slli_epi64 funziona su gcc/clang/icc anche quando il conteggio non è una costante in fase di compilazione (generazione di un movd da reg intero a xmm reg). C'è un _mm_sll_epi64 (__m128i a, __m128i count) (notare la mancanza di i), ma almeno in questi giorni, l'intrinseco i può generare una qualsiasi forma di psllq.


Le versioni conteggio di compilazione costante sono abbastanza efficienti, compiling to 4 instructions (o 5 senza AVX):

mm_bitshift_left_3(long long __vector(2)): 
     vpslldq xmm1, xmm0, 8 
     vpsrlq xmm1, xmm1, 61 
     vpsllq xmm0, xmm0, 3 
     vpor xmm0, xmm0, xmm1 
     ret 

Performance:

Questo ha 3 ciclo latenza (vpslldq (1) - > vpsrlq (1) -> vpor (1)) su Intel SnB/IvB/Haswell, con throughput limitato a uno per 2 cicli (saturando l'unità di spostamento vettoriale sulla porta 0). Byte-shift viene eseguito sull'unità shuffle su una porta diversa. I turni del conteggio immediato sono tutte istruzioni single-uop, quindi questo è solo 4 uops di dominio fusi che occupano lo spazio della pipeline quando vengono mescolati con altri codici. (Variable-count vettore turni sono 2 UOP, 2 latenza del ciclo, così la versione variabile count di questa funzione è peggio di quello che sembra dalle istruzioni di conteggio.)

O per i conteggi> = 64:

mm_bitshift_left_100(long long __vector(2)): 
     vpslldq xmm0, xmm0, 8 
     vpsllq xmm0, xmm0, 36 
     ret 

Se il numero di turni è non una costante in fase di compilazione, è necessario ramificarsi su conteggio> 64 per capire se spostare a sinistra o destra il carry.Credo che il numero di turni sia interpretato come un numero intero senza segno, quindi un conteggio negativo è impossibile.

Richiede inoltre ulteriori istruzioni per ottenere il conteggio int e il numero di 64 in registri vettoriali. Facendo questo in un modo senza fili con il confronto vettoriale e un'istruzione di fusione potrebbe essere possibile, ma un ramo è probabilmente una buona idea.


La versione variabile conteggio per __uint128_t nel GP registri sembra abbastanza buona; meglio della versione SSE. Clang does a slightly better job than gcc, emitting fewer mov instructions, ma utilizza ancora due istruzioni cmov per il conteggio> = 64 casi. (. Poiché le istruzioni di turno intero x86 mascherare il conteggio, invece di saturare)

__uint128_t leftshift_int128(__uint128_t x, unsigned count) { 
    return x << count; // undefined if count >= 128 
} 
+0

Grazie mille. Sfortunatamente 'count' non è una costante in fase di compilazione. Comunque metterò alla prova entrambi i suggerimenti. – user0

+0

Secondo i miei test, il mio vecchio codice scritto da 4 'int64_t' vars è più veloce (> 2 volte) per il conteggio' generato a caso '; ma per il conteggio della costante in fase di compilazione, 'mm_bitshift_left' è almeno 1,5 volte più veloce. – user0

+0

@ user0: Non sono sorpreso. In una vera app, mi aspetto che ci sia un po 'di prevedibilità nel numero di turni. Inoltre, il tuo microbench ha testato * solo * il turno o ha provato il turno come un'operazione tra due altri elementi intrinseci del vettore? In tal caso, lo spostamento di 'int64_t' dovrebbe ottenere i valori dal vettore ai reg di GP e viceversa. (Penso di aver detto nella mia risposta che se i tuoi dati non sono già nei reg di vettori, lo spostamento di '__uint128' (o è equivalente a mano con' int64_t') dovrebbe andare bene.) –

Problemi correlati