¿Por qué la latencia de la instrucción sqrtsd cambia según la entrada? Procesadores Intel

9

Bien en la guía intrínseca de Intel se afirma que la instrucción llamada "sqrtsd" tiene una latencia de 18 ciclos.

Lo probé con mi propio programa y es correcto si, por ejemplo, tomamos 0.15 como entrada. Pero cuando tomamos 256 (o cualquier número 2 ^ x), la latencia es solo 13. ¿Por qué es eso?

Una teoría que tenía es que dado que 13 es la latencia de "sqrtss", que es lo mismo que "sqrtsd" pero hecho en puntos flotantes de 32 bits, entonces el procesador fue lo suficientemente inteligente como para entender que 256 puede caber en 32 bits y, por lo tanto, usar esa versión mientras que 0.15 necesita los 64 bits completos ya que no es representable de manera finita.

Lo estoy haciendo usando el ensamblaje en línea, aquí está la parte relevante compilada con gcc -O3 y -fno-tree-vectorize.

static double sqrtsd (double x) {
    double r;
    __asm__ ("sqrtsd %1, %0" : "=x" (r) : "x" (x));
    return r;
}
Tommy95
fuente
3
Muéstranos el código de la prueba. Puedo imaginar la implementación donde la optimización realizada por el compilador y no el procesador.
Robert Navado
Los procesadores no son inteligentes: realizan las instrucciones dadas.
Veleta
¿Responde esto a tu pregunta? ¿Por qué SSE scalar sqrt (x) es más lento que rsqrt (x) * x?
Tarek Dakhran
2
No estás imaginando cosas: instlatx64 para skylake también enumera 18 (peor caso) y 13 (valores simples)
harold
1
Su asm en línea no tiene sentido y no compilará: godbolt.org/z/rJA6nS . "i"especifica es un inmediato y no puede ser una restricción de salida. sqrtsdsolo acepta una entrada reg / mem, no inmediata, por lo que no se ensamblaría incluso si se compilara. Además, el uso de inmediatos constantes de tiempo de compilación no le permite probar la latencia, solo el rendimiento. Pero sus números parecen cuerdos, así que sea lo que sea que haya hecho, probablemente haya probado la latencia sqrtsd.
Peter Cordes

Respuestas:

10

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.0no es rápido, aunque es exactamente representable y también lo es el 3.0resultado. 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.

( +-Infy también NaNson 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 divrendimiento 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, xmm2para 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 sqrtssdejar 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 sqrtssuna dependencia falsa). AVX finalmente nos permite evitar esto vsqrtsd dst, src,srcal menos para las fuentes de registro, y de manera similar vcvtsi2sd dst, cold_reg, eaxpara 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.

Peter Cordes
fuente
Por cierto, ¿qué pasa con las latencias entre los dos extremos? ¿Suceden? No pude hacer que sucediera en mi Haswell, pero eso no es concluyente
Harold
@harold: IDK, supongo que si fuera posible, sucedería con un número menor de ceros en la mantisa. Pero tal vez solo haya un detector de salida anticipada de casos especiales para los casos más simples. El divisor de radix inferior de Haswell debería hacer que sea más rentable buscar una salida anticipada antes, pero tal vez sea una cuestión de que la estimación inicial (de la misma tabla que usa rsqrt) sea exacta o no, y si no, entonces necesita refinamiento iterativo. camino al final.
Peter Cordes
rsqrtSin embargo, no es exacto para las potencias de dos (en Haswell de todos modos), pero las potencias de dos y cero son hasta ahora las únicas entradas que he encontrado donde la raíz cuadrada es rápida, entonces nuevamente las rsqrtinstrucciones parecen hacer más que una simple búsqueda dada cuánto dura realmente su latencia
harold
@harold: rsqrtpodría no ser la salida sin formato de la LUT (sí, como usted editó, la alta latencia podría ser un poco de trabajo). O tal vez conduce a la respuesta exacta para entradas simples (mantisa todo cero). O tal vez la mantisa totalmente cero puede omitir la búsqueda de LUT antes de comenzar el refinamiento. No sé lo suficiente sobre los divisores HW para descartar cualquiera de estas conjeturas. : /
Peter Cordes
1
¿Es sqrtsdrápido para potencias de dos con exponentes impares? ¿O solo para potencias de dos con exponentes pares? Esto es interesante.
fuz