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
}
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. –
Vedere http://stackoverflow.com/questions/9980801/looking-for-sse-128-bit-shift-operation-for-non-immediate-shift-value –
Si stanno elaborando flussi di bit o variabili aritmetiche (int galleggia, ecc.)? – bazza