2012-02-19 25 views
7

Necesito una función de una variable __m128i que tenga un período de 2^128. No necesita aumentar monótonamente (como un contador), pero visite cada valor una vez.contador SSE de 128 bits

El ejemplo más simple que pude pensar es en realidad un contador de 128 bits, pero me pareció difícil de implementar en SSE. ¿Hay alguna solución más simple/más rápida?

+2

¿Por qué necesita visitar 2^128 valores? Ninguna computadora en la tierra es capaz de hacer eso. ¿No puedes usar un int de 64 bits? – usr

+0

De acuerdo, en un procesador con una velocidad de reloj del orden de gigahertz, puede consumir un número en cada ciclo durante aproximadamente 584 años antes de que se agote un contador de 64 bits. – Damon

Respuesta

5

Aquí hay un contador monótono. Aunque no estoy seguro si puedes llamarlo simple.

Suponiendo que ONE y ZERO están siempre en registros, entonces esto debería compilarse a 5 instrucciones. (7 o 8 si VEX-codificación no se utiliza)

inline __m128i nextc(__m128i x){ 
    const __m128i ONE = _mm_setr_epi32(1,0,0,0); 
    const __m128i ZERO = _mm_setzero_si128(); 

    x = _mm_add_epi64(x,ONE); 
    __m128i t = _mm_cmpeq_epi64(x,ZERO); 
    t = _mm_and_si128(t,ONE); 
    t = _mm_unpacklo_epi64(ZERO,t); 
    x = _mm_add_epi64(x,t); 

    return x; 
} 

Código de prueba (MSVC):

int main() { 

    __m128i x = _mm_setr_epi32(0xfffffffa,0xffffffff,1,0); 

    int c = 0; 
    while (c++ < 10){ 
     cout << x.m128i_u64[0] << " " << x.m128i_u64[1] << endl; 
     x = nextc(x); 
    } 

    return 0; 
} 

de salida:

18446744073709551610 1 
18446744073709551611 1 
18446744073709551612 1 
18446744073709551613 1 
18446744073709551614 1 
18446744073709551615 1 
0 2 
1 2 
2 2 
3 2 

Ligeramente mejor versión sugerido por @ Norbert P. Guarda 1 instrucción sobre mi solución original.

inline __m128i nextc(__m128i x){ 
    const __m128i ONE = _mm_setr_epi32(1,0,0,0); 
    const __m128i ZERO = _mm_setzero_si128(); 

    x = _mm_add_epi64(x,ONE); 
    __m128i t = _mm_cmpeq_epi64(x,ZERO); 
    t = _mm_unpacklo_epi64(ZERO,t); 
    x = _mm_sub_epi64(x,t); 

    return x; 
} 
+0

Gracias, su código es mucho más limpio que el mío. Esperaré un poco para ver si hay otras soluciones además de los contadores. – jk4736

+0

La pregunta es, si esto es realmente más rápido que la solución que no usa ninguna SSE. Me refiero a que la solución obvia que usa una estructura de 2 no firmados de 64 bits y una rama evitaría la posible sobrecarga de SSE y las ramas serían extremadamente predecibles. – Voo

+0

@Voo Probablemente dependerá de en qué forma se necesite el valor.Si es necesario en registros generales o en memoria, entonces un 'add + adc' sería más rápido. Si es necesario en un registro SSE, entonces 5 instrucciones SSE-Int probablemente serán más rápidas que cualquier tipo de extracción/inserción. Pasarlo a través de la memoria a través de una estructura/unión probablemente bloqueará los almacenamientos intermedios de carga/almacenamiento ya que está accediendo a la misma memoria con un tamaño de palabra diferente. – Mysticial

4

Nunca olvide el principio KISS.

pegar (se requieren enteros sin signo para envolver alrededor de la norma C, por lo tanto visitar cada valor sólo una vez) esto:

__uint128_t inc(__uint128_t x) { 
    return x+1; 
} 

en this rendimientos (para x64):

addq $1, %rdi 
    adcq $0, %rsi 
    movq %rdi, %rax 
    movq %rsi, %rdx 
    ret 

fácil /¿suficientemente rapido? Si en línea que, es probable que sea capaz de salirse con sólo los addq/adcq (los movq s ret y está obligado por la ABI x64: si la función en línea, que no están obligados)


para abordar el comentario de Voo sobre la suckiness de MSVC, puede utilizar el siguiente:

inline void inc(unsigned long long *x, unsigned long long *y) { 
    if (!++*x) ++*y; // yay for obfuscation! 
} 

no tengo un MSVC instalar cerca, así que no puedo probarlo, pero debería rendimiento algo similar a lo que publique arriba. Entonces, si realmente necesita un __m128i, debería poder cast las dos mitades.

+0

El problema con esta solución es que la carga y el almacenamiento en registros SSE tomará mucho tiempo. – jk4736

+0

Depende enormemente del compilador si ese código es válido o no. Bastante seguro de que no está bajo MSVC - __m128 es solo una estructura de una unión de primitivos más pequeños. – Voo

+0

@ jk4736 todos los registros en mi fragmento son no SSE de 64 bits de ancho ... – CAFxX

Cuestiones relacionadas