c++ visual-c++ avx fma

c++ - ¿Cómo sacar datos de los registros AVX?



visual-c++ fma (3)

Utilizando MSVC 2013 y AVX 1, tengo 8 carrozas en un registro:

__m256 foo = mm256_fmadd_ps(a,b,c);

Ahora quiero llamar a la inline void print(float) {...} para los 8 flotadores. Parece que los intrísicos de Intel AVX harían esto bastante complicado:

print(_castu32_f32(_mm256_extract_epi32(foo, 0))); print(_castu32_f32(_mm256_extract_epi32(foo, 1))); print(_castu32_f32(_mm256_extract_epi32(foo, 2))); // ...

pero MSVC ni siquiera tiene ninguno de estos dos intrínsecos. Claro, podría volver a escribir los valores en la memoria y cargar desde allí, pero sospecho que a nivel de ensamblaje no hay necesidad de derramar un registro.

Bono Q: Por supuesto que me gustaría escribir

for(int i = 0; i !=8; ++i) print(_castu32_f32(_mm256_extract_epi32(foo, i)))

pero MSVC no entiende que muchos intrínsecos requieren desenrollar el bucle. ¿Cómo escribo un ciclo sobre los flotadores de 8x32 en __m256 foo ?


Cuidado: _mm256_fmadd_ps no es parte de AVX1. FMA3 tiene su propio bit de características, y solo se introdujo en Intel con Haswell. AMD introdujo FMA3 con Piledriver (AVX1 + FMA4 + FMA3, no AVX2).

En el nivel de ASM, si desea obtener ocho elementos de 32 bits en registros enteros, en realidad es más rápido almacenarlos en la pila y luego hacer cargas escalares. pextrd es una instrucción 2-uop en la familia SnB y en la familia Bulldozer. (y Nehalem y Silvermont, que no son compatibles con AVX).

La única CPU donde vextractf128 + 2x movd + 6x pextrd no es terrible es AMD Jaguar. ( pextrd barato, y solo un puerto de carga). (Ver tablas de ins de Agner Fog )

Un almacén alineado ancho puede reenviar a cargas estrechas superpuestas. (Por supuesto, puede usar movd para obtener el elemento bajo, por lo que tiene una mezcla de puerto de carga y UU de puerto ALU).

Por supuesto, parece que está extrayendo float utilizando un extracto entero y luego convirtiéndolo de nuevo en un flotador. Eso parece horrible.

Lo que realmente necesita es cada float en el elemento bajo de su propio registro xmm. vextractf128 es obviamente la manera de comenzar, llevando el elemento 4 al final de un nuevo registro xmm. Luego, 6x AVX shufps pueden obtener fácilmente los otros tres elementos de cada mitad. (O movshdup y movhlps tienen codificaciones más cortas: no hay byte inmediato).

7 shuffle uops valen la pena considerar frente a 1 tienda y 7 de carga uops, pero no si vas a derramar el vector para una llamada de función de todos modos.

Consideraciones ABI:

Estás en Windows, donde xmm6-15 son llamadas preservadas (solo el low128, las mitades superiores de ymm6-15 son call-cloked). Esta es otra razón más para comenzar con vextractf128 .

En SysV ABI, todos los registros xmm / ymm / zmm son eliminados por llamada, por lo que cada función de print() requiere un derrame / recarga. Lo único que hay que hacer es almacenar en la memoria e print con el vector original (es decir, imprimir el elemento bajo porque ignorará el resto del registro). Luego movss xmm0, [rsp+4] e print llamada en el segundo elemento, etc.

No es bueno tener las 8 carrozas muy bien desempaquetadas en 8 vectores, ¡porque todos deberían ser separados por separado antes de la primera llamada a la función!


Suponiendo que solo tiene AVX (es decir, no AVX2), entonces podría hacer algo como esto:

float extract_float(const __m128 v, const int i) { float x; _MM_EXTRACT_FLOAT(x, v, i); return x; } void print(const __m128 v) { print(extract_float(v, 0)); print(extract_float(v, 1)); print(extract_float(v, 2)); print(extract_float(v, 3)); } void print(const __m256 v) { print(_mm256_extractf128_ps(v, 0)); print(_mm256_extractf128_ps(v, 1)); }

Sin embargo, creo que probablemente solo use una unión:

union U256f { __m256 v; float a[8]; }; void print(const __m256 v) { const U256f u = { v }; for (int i = 0; i < 8; ++i) print(u.a[i]); }


(Respuesta inacabada. Publicación de todos modos en caso de que ayude a alguien, o en caso de que regrese. Generalmente, si necesita interactuar con escalar que no puede vectorizar, no está mal almacenar un vector en una matriz local, y luego, vuelva a cargarlo un elemento a la vez ).

Vea mi otra respuesta para los detalles del asm. Esta respuesta es sobre el lado C ++ de las cosas.

Utilizando la Biblioteca de clases de vector de Agner Fog , su operator[] sobrecarga de clases de envoltura operator[] funciona exactamente de la manera esperada, incluso para argumentos no constantes. Esto a menudo se compila en una tienda / recarga, pero hace que sea fácil escribir el código en C ++. Con la optimización habilitada, es probable que obtenga resultados decentes. (excepto que el elemento bajo puede almacenarse / recargarse, en lugar de ser usado en su lugar. Por lo tanto, es posible que necesite un _mm_cvtss_f32(vec) especial vec[0] en _mm_cvtss_f32(vec) o algo).

Vea también mi repositorio github con cambios mayormente no comprobados en la VCL de Agner, para generar mejor código para algunas funciones.

Hay una macro de envoltura _MM_EXTRACT_FLOAT , pero es extraña y solo se ha definido con SSE4.1. Creo que está destinado a ir con SSE4.1 extractps (que puede extraer la representación binaria de un flotante en un registro entero, o almacenar en la memoria). Sin embargo, gcc sí lo compila en una mezcla FP cuando el destino es un float . Tenga cuidado de que otros compiladores no lo compilen con una instrucción de extractps real si quiere que el resultado sea float , porque eso no es lo que hace la extractps . (Eso es lo que hace el insertps , pero un shuffle FP más simple tomaría menos bytes de instrucción, por ejemplo shufps con AVX es genial).

Es extraño porque lleva 3 _MM_EXTRACT_FLOAT(dest, src_m128, idx) : _MM_EXTRACT_FLOAT(dest, src_m128, idx) , por lo que ni siquiera puede usarlo como inicializador para un float local.

Para recorrer un vector

gcc desenrollará un bucle como ese para usted, pero solo con -O1 o superior. En -O0 , le dará un mensaje de error.

float bad_hsum(__m128 & fv) { float sum = 0; for (int i=0 ; i<4 ; i++) { float f; _MM_EXTRACT_FLOAT(f, fv, i); // works only with -O1 or higher sum += f; } return sum; }