2012-09-11 18 views
6

Estoy usando la extensión de vector GCC SIMD para un proyecto, todo funciona bastante bien pero se lanza, simplemente restablecen todos los componentes de un vector.Cómo lanzar vectores SIMD int para flotar en GCC?

Los manual estados:

Es posible lanzar desde un tipo de vector a otro, siempre que sean del mismo tamaño (de hecho, también se pueden lanzar vectores hacia y desde otros tipos de datos de la misma tamaño).

Aquí está un ejemplo sencillo:

#include <stdio.h> 

typedef int int4 __attribute__ ((vector_size(sizeof(int) * 4))); 
typedef float float4 __attribute__ ((vector_size(sizeof(float) * 4))); 

int main() 
{ 
    int4 i = { 1 , 2 , 3 , 4 }; 
    float4 f = { 0.1 , 0.2 , 0.3 , 0.4 }; 

    printf("%i %i %i %i\n" , i[0] , i[1] , i[2] , i[3]); 
    printf("%f %f %f %f\n" , f[0] , f[1] , f[2] , f[3]); 

    f = (float4)i; 

    printf("%f %f %f %f\n" , f[0] , f[1] , f[2] , f[3]); 
} 

Compilar con gcc cast.c -O3 -o cast y funcionando en mi máquina me sale:

1 2 3 4 
0.100000 0.200000 0.300000 0.400000 
0.000000 0.000000 0.000000 0.000000 <-- no no no 

No soy ese guru ensamblador, pero acabo de ver algunos movimientos byte aquí:

 
[...] 
400454:  f2 0f 10 1d 1c 02 00 movsd 0x21c(%rip),%xmm3 
40045b:  00 
40045c:  bf 49 06 40 00   mov $0x400649,%edi 
400461:  f2 0f 10 15 17 02 00 movsd 0x217(%rip),%xmm2 
400468:  00 
400469:  b8 04 00 00 00   mov $0x4,%eax 
40046e:  f2 0f 10 0d 12 02 00 movsd 0x212(%rip),%xmm1 
400475:  00 
400476:  f2 0f 10 05 12 02 00 movsd 0x212(%rip),%xmm0 
40047d:  00 
40047e:  48 83 c4 08    add $0x8,%rsp 
400482:  e9 59 ff ff ff   jmpq 4003e0 

I s use el vector equivalente al escalar:

*(int *)&float_value = int_value; 

¿Cómo se puede explicar este comportamiento?

+3

Sí, eso es lo que parece que está sucediendo - una conversión de bit a bit. (o más bien, no hay conversión en absoluto) Así que obtienes 4 flotadores desnormalizados en lugar de una conversión de valor real. – Mysticial

+1

Eso es lo que los moldes de vectores están definidos para hacer (cualquier otra cosa sería completamente loca, y haría que las expresiones de programación de vectores estándar fueran muy dolorosas de escribir). Si desea obtener una conversión, probablemente desee utilizar un tipo de intrínseco, como '_mm_cvtepi32_ps' (esto rompe la buena independencia arquitectónica de su código de vector, por supuesto, que también es molesto, un enfoque común es usar un encabezado de traducción que define un conjunto portátil de "intrínsecos"). –

+0

Puedo ver su punto, pero la pregunta es: ¿cuándo será útil este _cast_? – cYrus

Respuesta

8

Eso es lo que definen los moldes de vectores para hacer (cualquier otra cosa sería completamente delirante, y haría que los lenguajes de programación de vectores estándar fueran muy dolorosos de escribir). Si desea obtener una conversión, probablemente desee utilizar un intrínseco de algún tipo, como _mm_cvtepi32_ps (esto rompe la buena independencia arquitectónica de su código de vector, por supuesto, que también es molesto, un enfoque común es usar un encabezado de traducción que define un conjunto portátil de "intrínsecos").

¿Por qué es esto útil? Varias razones, pero esta es la más grande:

En código vectorial, casi nunca desea realizar una bifurcación. En cambio, si necesita hacer algo de manera condicional, evalúa ambos lados de la condición y usa una máscara para seleccionar el carril del resultado apropiado por carril. Estos vectores de máscara "naturalmente" tienen un tipo entero, mientras que sus vectores de datos a menudo son de coma flotante; desea combinar los dos usando operaciones lógicas. Esta expresión extremadamente común es más natural si los moldes de vectores simplemente reinterpretan los bits.

Concedido, es posible trabajar con este caso o cualquier bolsa de otros idiomas comunes, pero la vista "vector es una bolsa de bits" es muy común y refleja la forma en que la mayoría de los programadores de vectores piensan.

2

De hecho, ni siquiera se está generando una instrucción de vector único en su caso y no se está realizando ningún tipo de conversión en tiempo de ejecución. Todo se hace en tiempo de compilación debido al interruptor -O3. Las cuatro instrucciones MOVSD están cargando los argumentos preconvertidos al printf. De hecho, de acuerdo con SysV AMD64 ABI, los argumentos de coma flotante se pasan en los registros de XMM.La sección que ha desmontado es (código ensamblador obtenido de la compilación con -S):

movsd .LC6(%rip), %xmm3 
    movl $.LC5, %edi 
    movsd .LC7(%rip), %xmm2 
    movl $4, %eax 
    movsd .LC8(%rip), %xmm1 
    movsd .LC9(%rip), %xmm0 
    addq $8, %rsp 
    .cfi_def_cfa_offset 8 
    jmp  printf 
    .cfi_endproc 

.LC5 etiquetas de la cadena de formato:

.LC5: 
    .string "%f %f %f %f\n" 

el puntero a la cadena de formato es de la clase INTEGER y así se pasa en el registro RDI (estando en algún lugar en los primeros 4 GiB del espacio VA, algunos bytes de código se guardan emitiendo un movimiento de 32 bits a la parte inferior de RDI). El registro RAX (EAX usado para guardar en bytes de código) se carga con la cantidad de argumentos pasados ​​en los registros XMM (nuevamente de acuerdo con SysV AMD64 ABI para llamadas a funciones con número de argumentos variable). Los cuatro MOVSD (MOVe Scalar Double-precision) mueven los argumentos correspondientes en los registros XMM. .LC9 por ejemplo etiquetas de dos palabras dobles:

.align 8 
.LC9: 
    .long 0 
    .long 916455424 

Los dos forman la palabra cuádruple de 64 bits 0x36A0000000000000 que pasa a ser 2 -149 en 64 bits IEEE representación 754. En desnormalizaremos 32 bits IEEE 754 se ve como 0x00000001, por lo que de hecho es un no conversión del número entero 1 (pero desde printf espera double argumentos todavía se preconvertido a doble precisión). El segundo argumento es:

.align 8 
.LC8: 
    .long 0 
    .long 917504000 

Este es 0x36B0000000000000 o 2 -148 en 64 bits IEEE 754 y 0x00000002 en desnormalizaremos 32 bits IEEE 754. Se va en el mismo para las otras dos argumentos.

Tenga en cuenta que el código anterior no utiliza una única variable de pila - opera con sólo constantes calculados previamente. Esto resulta de usar un nivel de optimización muy alto (-O3). Se produce una conversión de tiempo de ejecución real si compila con un nivel de optimización inferior (-O2 o inferior). El siguiente código es entonces emitida para realizar el encasillado:

movaps -16(%rbp), %xmm0 
    movaps %xmm0, -32(%rbp) 

Esto sólo mueve los cuatro valores enteros en las ranuras correspondientes del vector de punto flotante, por lo tanto, ninguna conversión en absoluto. Luego, para cada elemento de alguna SSE galimatías se lleva a cabo con el fin de convertirlo de precisión simple a doble precisión (como era de esperar por printf):

movss -20(%rbp), %xmm0 
    unpcklps  %xmm0, %xmm0 
    cvtps2pd  %xmm0, %xmm3 

(¿por qué no usar CVTSS2SD está más allá de mi comprensión de la instrucción SSE SET)

+0

¡Gracias por la aclaración! – cYrus

+0

¡De nada! –

1

puede convertir de int a flotar al enrollarlo sobre los elementos directamente

float4 cast(int4 x) { 
    float4 y; 
    for(int i=0; i<4; i++) y[i] = x[i]; 
    return y; 
} 

GCC, Clang, e ICC todo generar una instrucción cvtdq2ps xmm0, xmm0 para esto.

https://godbolt.org/g/KU1aPg