c++ - ¿Qué tan caro es convertir entre int y doble?
x86 c++-cli (2)
A menudo veo el código que convierte de ints a doblots en tots a dots y de vuelta una vez más (a veces por buenas razones, a veces no), y se me ocurrió que esto parece ser un costo "oculto" en mi programa. Supongamos que el método de conversión es el truncamiento.
Entonces, ¿qué tan caro es? Estoy seguro de que varía según el hardware, así que supongamos que es un procesador Intel nuevo (Haswell, si lo desea, aunque tomaré cualquier cosa). Algunas métricas que me interesarían (aunque una buena respuesta no tiene que tener todas):
- # de instrucciones generadas
- # de ciclos utilizados
- Costo relativo comparado con operaciones aritméticas básicas.
También supondría que la forma en que experimentaría de manera más aguda el impacto de una conversión lenta sería con respecto al uso de energía en lugar de la velocidad de ejecución, dada la diferencia en cuántos cálculos podemos realizar cada segundo en relación con la cantidad de datos que realmente pueden llegar. en la CPU cada segundo.
Esto es lo que pude encontrar yo mismo, para x86-64 haciendo FP FP con SSE2 (no legado x87 donde cambiar el modo de redondeo para la semántica de truncamiento de C ++ era caro):
Cuando miro el ensamblado generado desde clang y gcc, parece que el reparto
int
double
, se reduce a una sola instrucción:cvttsd2si
.De
double
aint
escvtsi2sd
. (cvtsi2sdl
AT&Tcvtsi2sd
paracvtsi2sd
con tamaño de operando de 32 bits).Con la auto-vectorización, obtenemos
cvtdq2pd
.Entonces supongo que la pregunta es: ¿cuál es el costo de esos ?
Estas instrucciones cuestan aproximadamente lo mismo que un FP
addsd
y unmovq xmm, r64
(fp <- integer) omovq r64, xmm
(integer <- fp), porque se decodifican a 2 uops que en los mismos puertos, en mainstream ( Sandybridge / Haswell / Sklake) CPU de Intel.El Manual de referencia de optimización de arquitecturas Intel® 64 e IA-32 dice que el costo de la instrucción
cvttsd2si
es de 5 latencias (consulte el Apéndice C-16).cvtsi2sd
, dependiendo de su arquitectura, tiene una latencia que varía desde 1 en Silvermont hasta más o menos 7-16 en otras arquitecturas.Las tablas de instrucciones de Agner Fog tienen números más precisos / sensibles, como la latencia de 5 ciclos para
cvtsi2sd
en Silvermont (con 1 por 2 de rendimiento de reloj), o 4c de latencia en Haswell, con una por rendimiento de reloj (si evita la dependencia en el registro de destino de fusionarse con la mitad superior anterior, como gcc suele hacer conpxor xmm0,xmm0
).SIMD empacado -
float
a empacado -int
es genial; solo uop. Pero convertir aldouble
requiere un orden aleatorio para cambiar el tamaño del elemento. SIMD float / double <-> int64_t no existe hasta AVX512, pero se puede hacer manualmente con un rango limitado.El manual de Intel define la latencia como: "La cantidad de ciclos de reloj que se requieren para que el núcleo de ejecución complete la ejecución de todos los μops que forman una instrucción". Pero una definición más útil es la cantidad de relojes de una entrada que está lista hasta que la salida esté lista. El rendimiento es más importante que la latencia si hay suficiente paralelismo para que la ejecución fuera de orden haga su trabajo: ¿Qué consideraciones entran en la predicción de la latencia para las operaciones en los procesadores superscalar modernos y cómo puedo calcularlas manualmente? .
El mismo manual de Intel dice que los costos de instrucción de
add
un entero 1 latencia y los costos de un entero entero 3 (Apéndice C-27). La FPaddsd
ymulsd
ejecuta a 2 por rendimiento de reloj, con latencia de 4 ciclos, en Skylake. Lo mismo para las versiones SIMD, y para FMA, con vectores de 128 o 256 bits.En Haswell,
addsd
/addpd
tiene solo 1 rendimiento por reloj, pero una latencia de 3 ciclos gracias a una unidad dedicada para agregar FP.
Entonces, la respuesta se reduce a:
1) Es hardware optimizado, y el compilador aprovecha la maquinaria de hardware.
2) Cuesta solo un poco más de lo que lo hace la multiplicación en términos del número de ciclos en una dirección y una cantidad altamente variable en la otra (según su arquitectura). Su costo no es ni libre ni absurdo, pero probablemente merece más atención dado lo fácil que es escribir código que incurre en el costo de una manera no obvia.
Por supuesto, este tipo de pregunta depende del hardware exacto e incluso del modo.
En x86 mi i7 cuando se usa en el modo de 32 bits con opciones predeterminadas ( gcc -m32 -O3
) la conversión de int
a double
es bastante rápida, lo contrario es mucho más lento porque el estándar C exige una regla absurda (truncamiento de decimales) .
Esta forma de redondeo es mala tanto para las matemáticas como para el hardware y requiere que la FPU cambie a este modo especial de redondeo, realice el truncamiento y vuelva a una forma sana de redondeo.
Si necesita velocidad, la conversión fistp
> int utilizando la simple instrucción fistp
es más rápida y mucho mejor para los resultados de cómputo, pero requiere un poco de ensamblaje en línea.
inline int my_int(double x)
{
int r;
asm ("fldl %1/n"
"fistpl %0/n"
:"=m"(r)
:"m"(x));
return r;
}
es más de 6 veces más rápido que el ingenuo x = (int)y;
conversión (y no tiene un sesgo hacia 0).
Sin embargo, el mismo procesador, cuando se usa en el modo de 64 bits, no tiene problemas de velocidad y el uso del código fistp
hace que el código se ejecute un poco más lento.
Al parecer, los tipos de hardware se dieron por vencidos e implementaron el algoritmo de redondeo incorrecto directamente en el hardware (por lo que el código de redondeo puede ejecutarse rápidamente).