SQRT * y DIV * son las dos únicas instrucciones ALU "simples" (uop único, no ramificación / bucle microcodificado) que tienen un rendimiento o latencia dependiente de los datos en las CPU Intel / AMD modernas. (Sin contar el microcódigo ayuda a los valores de FP denormales, también conocidos como subnormales, en add / multiply / fma). Todo lo demás está bastante arreglado, por lo que la maquinaria de programación de UOP fuera de servicio no necesita esperar la confirmación de que un resultado estuvo listo en algún ciclo, solo sabe que lo estará.
Como de costumbre, la guía intrínseca de Intel ofrece una imagen simplificada del rendimiento. La latencia real no es de 18 ciclos fijos para doble precisión en Skylake. (Según los números que eligió citar, supongo que tiene un Skylake).
div / sqrt son difíciles de implementar; Incluso en hardware, lo mejor que podemos hacer es un proceso de refinamiento iterativo. La refinación de más bits a la vez (divisor radix-1024 desde Broadwell) lo acelera (consulte estas preguntas y respuestas sobre el hardware ). Pero todavía es lo suficientemente lento como para que se use una salida anticipada para acelerar casos simples (o tal vez el mecanismo de aceleración simplemente está omitiendo un paso de configuración para mantisas sin cero en CPU modernas con unidades div / sqrt parcialmente canalizadas. Las CPU más antiguas tenían un rendimiento = latencia para FP div / sqrt; esa unidad de ejecución es más difícil de canalizar).
https://www.uops.info/html-instr/VSQRTSD_XMM_XMM_XMM.html muestra que Skylake SQRTSD puede variar de 13 a 19 ciclos de latencia. Los números SKL (cliente) solo muestran una latencia de 13 ciclos, pero podemos ver en la página detallada de SKL vsqrtsd que solo probaron con input = 0. Los números SKX (servidor) muestran una latencia de 13-19 ciclos. ( Esta página tiene el desglose detallado del código de prueba que usaron, incluidos los patrones de bits binarios para las pruebas). Se realizaron pruebas similares (con solo 0 para núcleos de clientes) en lasqrtsd xmm, xmm
página que no es VEX . : /
Los resultados de InstLatx64 muestran latencias en el mejor / peor de los casos de 13 a 18 ciclos en Skylake-X (que usa el mismo núcleo que Skylake-client, pero con AVX512 habilitado).
Las tablas de instrucciones de Agner Fog muestran una latencia de 15-16 ciclos en Skylake. (Agner normalmente prueba con un rango de valores de entrada diferentes). Sus pruebas son menos automatizadas y algunas veces no coinciden exactamente con otros resultados.
¿Qué hace que algunos casos sean rápidos?
Tenga en cuenta que la mayoría de los ISA (incluido x86) utilizan coma flotante binaria :
los bits representan valores como un significado lineal (también conocido como mantisa) multiplicado por 2 exp , y un bit de signo.
Parece que solo puede haber 2 velocidades en Intel moderno (al menos desde Haswell) (vea la discusión con @harold en los comentarios). Por ejemplo, incluso las potencias de 2 son rápidas, como 0.25, 1, 4 y 16. Estas son triviales mantisa = 0x0 que representa 1.0. https://www.h-schmidt.net/FloatConverter/IEEE754.html tiene un buen convertidor interactivo de patrones de bits <-> decimales para precisión simple, con casillas de verificación para los bits establecidos y las anotaciones de lo que representan la mantisa y el exponente.
En Skylake, los únicos casos rápidos que he encontrado en una comprobación rápida son incluso potencias de 2 como 4.0 pero no 2.0. Estos números tienen un resultado sqrt exacto con entrada y salida con 1.0 mantisa (solo el conjunto implícito de 1 bit). 9.0
no es rápido, aunque es exactamente representable y también lo es el 3.0
resultado. 3.0 tiene mantissa = 1.5 con solo el bit más significativo del conjunto de mantisa en la representación binaria. La mantisa de 9.0 es 1.125 (0b00100 ...). Entonces, los bits distintos de cero están muy cerca de la parte superior, pero aparentemente eso es suficiente para descalificarlo.
( +-Inf
y también NaN
son rápidos. También lo son los números negativos ordinarios: resultado = -NaN . Mido una latencia de 13 ciclos para estos en i7-6700k, lo mismo que para 4.0
. vs 18 latencia de ciclo para el caso lento).
x = sqrt(x)
es definitivamente rápido con x = 1.0
(mantisa todo cero, excepto el primer bit implícito). Tiene una entrada simple y una salida simple.
Con 2.0, la entrada también es simple (mantisa todo cero y exponente 1 más alto) pero la salida no es un número redondo. sqrt (2) es irracional y, por lo tanto, tiene infinitos bits distintos de cero en cualquier base. Aparentemente esto hace que sea lento en Skylake.
Las tablas de instrucciones de Agner Fog dicen que el div
rendimiento de la instrucción de enteros de AMD K10 depende del número de bits significativos en el dividendo (entrada), no del cociente, pero al buscar el microarchivo de Agner y las tablas de instrucciones no encontraron notas al pie o información sobre cómo sqrt es específicamente Depende de los datos.
En CPU más antiguas con FP sqrt aún más lento, puede haber más espacio para un rango de velocidades. Creo que el número de bits significativos en la mantisa de la entrada probablemente será relevante. Menos bits significativos (más ceros finales en el significado) lo hacen más rápido, si esto es correcto. Pero de nuevo, en Haswell / Skylake, los únicos casos rápidos parecen ser incluso poderes de 2.
Puede probar esto con algo que acople la salida de nuevo a la entrada sin romper la dependencia de datos, por ejemplo, andps xmm0, xmm1
/ orps xmm0, xmm2
para establecer un valor fijo en xmm0 que depende de la salida sqrtsd.
O una forma más simple de probar la latencia es aprovechar la falsa dependencia de salida desqrtsd xmm0, xmm1
- it y sqrtss
dejar los 64/32 bits superiores (respectivamente) del destino sin modificar, por lo tanto, el registro de salida también es una entrada para esa fusión. Supongo que así es como su ingenuo intento de inline-asm terminó con un cuello de botella en la latencia en lugar del rendimiento con el compilador seleccionando un registro diferente para la salida, por lo que podría volver a leer la misma entrada en un bucle. El asm en línea que ha añadido a su pregunta está totalmente roto y ni siquiera se compilará, pero tal vez su verdadero código utilizado "x"
(registro XMM) de entrada y salida limitaciones en lugar de "i"
(inmediata)?
Esta fuente NASM para un bucle de prueba ejecutable estático (para ejecutarse perf stat
) usa esa dependencia falsa con la codificación no VEX de sqrtsd
.
Esta verruga de diseño ISA es gracias a la optimización de Intel a corto plazo con SSE1 en Pentium III. P3 manejó registros de 128 bits internamente como dos mitades de 64 bits. Dejando la mitad superior sin modificar, deje que las instrucciones escalares se descodifiquen en una sola uop. (Pero eso todavía le da a PIII sqrtss
una dependencia falsa). AVX finalmente nos permite evitar esto vsqrtsd dst, src,src
al menos para las fuentes de registro, y de manera similar vcvtsi2sd dst, cold_reg, eax
para las instrucciones de conversión escalar int-> fp de diseño miope similar. (GCC-perdió la optimización de informes: 80586 , 89071 , 80571 ).
En muchas CPU anteriores, incluso el rendimiento era variable, pero Skylake reforzó los divisores lo suficiente como para que el programador siempre sepa que puede iniciar un nuevo div / sqrt uop 3 ciclos después de la última entrada de precisión simple.
Sin embargo, incluso el rendimiento de doble precisión de Skylake es variable: 4 a 6 ciclos después de la última entrada de doble precisión, si las tablas de instrucciones de Agner Fog son correctas.
https://uops.info/ muestra un rendimiento recíproco plano de 6c. (O el doble de largo para vectores de 256 bits; 128 bits y escalares pueden usar mitades separadas de los divisores SIMD anchos para obtener más rendimiento pero la misma latencia). Consulte también División de punto flotante versus multiplicación de punto flotante para algunos números de rendimiento / latencia extraídos de las tablas de instrucciones de Agner Fog.