En cada lenguaje de programación hay conjuntos de códigos de operación que se recomiendan sobre otros. He tratado de enumerarlos aquí, en orden de velocidad.
- Bitwise
- Suma / resta de enteros
- Multiplicación / división de enteros
- Comparación
- Flujo de control
- Suma / resta del flotador
- Float Multiplication / Division
Cuando necesite código de alto rendimiento, C ++ puede optimizarse manualmente en el ensamblaje, para usar instrucciones SIMD o un flujo de control más eficiente, tipos de datos, etc. Así que estoy tratando de entender si el tipo de datos (int32 / float32 / float64) o la operación utilizado ( *
, +
, &
) afecta al rendimiento en el nivel de la CPU.
- ¿Una sola multiplicación es más lenta en la CPU que una adición?
- En la teoría MCU, aprende que la velocidad de los códigos de operación está determinada por la cantidad de ciclos de CPU que se necesitan para ejecutar. Entonces, ¿significa que multiplicar toma 4 ciclos y sumar toma 2?
- ¿Cuáles son exactamente las características de velocidad de los códigos de operación básicos de control matemático y control?
- Si dos códigos de operación toman el mismo número de ciclos para ejecutarse, ¿entonces ambos pueden usarse indistintamente sin ninguna ganancia / pérdida de rendimiento?
- Se agradece cualquier otro detalle técnico que pueda compartir sobre el rendimiento de la CPU x86
c++
performance
optimization
Robinicks
fuente
fuente
Respuestas:
Las guías de optimización de Agner Fog son excelentes. Tiene guías, tablas de tiempos de instrucción y documentos sobre la microarquitectura de todos los diseños recientes de CPU x86 (desde Intel Pentium). Vea también algunos otros recursos vinculados desde /programming//tags/x86/info
Solo por diversión, responderé algunas de las preguntas (números de CPU Intel recientes). La elección de operaciones no es el factor principal para optimizar el código (a menos que pueda evitar la división).
Sí (a menos que sea por una potencia de 2). (3-4 veces la latencia, con solo un rendimiento por reloj en Intel). Sin embargo, no se salga de su camino para evitarlo, ya que es tan rápido como 2 o 3 agregados.
Consulte las tablas de instrucciones y la guía de microarquitectura de Agner Fog si desea saber exactamente : P. Ten cuidado con los saltos condicionales. Los saltos incondicionales (como las llamadas a funciones) tienen una pequeña sobrecarga, pero no mucho.
No, podrían competir por el mismo puerto de ejecución que otra cosa, o no. Depende de qué otras cadenas de dependencia pueda trabajar la CPU en paralelo. (En la práctica, no suele tomarse ninguna decisión útil. De vez en cuando surge que podría usar un desplazamiento de vectores o un desplazamiento aleatorio de vectores, que se ejecutan en diferentes puertos en las CPU de Intel. Pero cambio por bytes de todo el registro (
PSLLDQ
etc.) se ejecuta en la unidad aleatoria).Los documentos de microarquitectura de Agner Fog describen las canalizaciones de las CPU de Intel y AMD con suficiente detalle para determinar exactamente cuántos ciclos debe tomar un ciclo por iteración, y si el cuello de botella es el rendimiento de UOP, una cadena de dependencia o contención para un puerto de ejecución. Vea algunas de mis respuestas en StackOverflow, como esta o esta .
Además, http://www.realworldtech.com/haswell-cpu/ (y similar para diseños anteriores) es una lectura divertida si te gusta el diseño de CPU.
Aquí está su lista, ordenada para una CPU Haswell, basada en mis mejores huéspedes. Sin embargo, esta no es realmente una forma útil de pensar sobre las cosas para nada más que ajustar un bucle asm. Los efectos de predicción de caché / rama generalmente dominan, así que escriba su código para tener buenos patrones. Los números son muy manuales y tratan de tener en cuenta la alta latencia, incluso si el rendimiento no es un problema, o para generar más uops que obstruyen la tubería para que otras cosas sucedan en paralelo. Esp. los números de caché / rama están muy inventados. La latencia es importante para las dependencias transportadas en bucle, el rendimiento importa cuando cada iteración es independiente.
TL: DR estos números están compuestos según lo que estoy imaginando para un caso de uso "típico", en cuanto a compensaciones entre latencia, cuellos de botella en el puerto de ejecución y rendimiento de front-end (o paradas para cosas como fallas de sucursales ) No utilice estos números para ningún tipo de análisis de rendimiento serio .
cambio y rotación (conteo de tiempo de compilación) /
versiones vectoriales de todo esto (1 a 4 por rendimiento de ciclo, latencia de 1 ciclo)
tmp += 7
en un bucle en lugar detmp = i*7
)sum
variable. (Podría ponderar esto y fp mul tan bajo como 1 o tan alto como 5 dependiendo del caso de uso)._mm_insert_epi8
, etc.)y = x ? a : b
, oy = x >= 0
) (test / setcc
ocmov
)%
por una constante de tiempo de compilación (sin potencia de 2).PHADD
agregar valores dentro de un vector)Lo inventé totalmente basado en conjeturas . Si algo parece mal, es porque estaba pensando en un caso de uso diferente o por un error de edición.
El costo relativo de las cosas en las CPU AMD será similar, excepto que tienen desplazadores enteros más rápidos cuando el conteo de cambios es variable. Las CPU de la familia AMD Bulldozer son, por supuesto, más lentas en la mayoría de los códigos, por una variedad de razones. (Ryzen es bastante bueno en muchas cosas).
Tenga en cuenta que es realmente imposible reducir las cosas a un costo unidimensional . Además de errores de caché y errores de bifurcación, el cuello de botella en un bloque de código puede ser latencia, rendimiento total de UOP (frontend) o rendimiento de un puerto específico (puerto de ejecución).
Una operación "lenta" como la división FP puede ser muy barata si el código circundante mantiene a la CPU ocupada con otro trabajo . (el vector FP div o sqrt son 1 uop cada uno, solo tienen una latencia y un rendimiento deficientes. Solo bloquean la unidad de división, no todo el puerto de ejecución en el que se encuentra. Div entero es de varios uops). Entonces, si solo tiene una división de FP por cada ~ 20 mul y sumar, y hay otro trabajo para la CPU (por ejemplo, una iteración de bucle independiente), entonces el "costo" del FP div podría ser aproximadamente el mismo que un FP mul. Este es probablemente el mejor ejemplo de algo que es de bajo rendimiento cuando es todo lo que está haciendo, pero que se mezcla muy bien con otro código (cuando la latencia no es un factor), debido a los bajos niveles totales.
Tenga en cuenta que la división de enteros no es tan amigable con el código circundante: en Haswell, son 9 uops, con un rendimiento de 8-11c y latencia de 22-29c. (La división de 64 bits es mucho más lenta, incluso en Skylake). Por lo tanto, los números de latencia y rendimiento son algo similares a FP div, pero FP div es solo una uop.
Para ver ejemplos de análisis de una secuencia corta de insns para rendimiento, latencia y uops totales, vea algunas de mis respuestas SO:
sum += x[i] * y[i]
desenrolla con múltiples acumuladores de vectores para ocultar la latencia de FMA. Es bastante técnico y de bajo nivel, pero le muestra el tipo de salida en lenguaje ensamblador que desea que haga su compilador y por qué es importante.IDK si otras personas escriben respuestas SO, incluido este tipo de análisis. Me resulta mucho más fácil encontrar el mío, porque sé que entro en detalles a menudo y puedo recordar lo que he escrito.
fuente
Depende de la CPU en cuestión, pero para una CPU moderna, la lista es algo como esto:
Dependiendo de la CPU, puede haber un costo considerable para trabajar con tipos de datos de 64 bits.
Tus preguntas:
if
lo que razonablemente puede hacer con la aritmética.Y finalmente, si estás haciendo un juego, no te preocupes demasiado por todo esto, mejor concéntrate en hacer un buen juego que cortar los ciclos de la CPU.
fuente
Hice una prueba sobre la operación de números enteros que hizo un millón de bucles en x64_64, llegué a una breve conclusión como a continuación,
agregar --- 116 microsegundos
sub ---- 116 microsegundos
mul ---- 1036 microsegundos
div ---- 13037 microsegundos
los datos anteriores ya han reducido la sobrecarga inducida por el bucle,
fuente
Los manuales del procesador Intel son una descarga gratuita desde su sitio web. Son bastante grandes, pero técnicamente pueden responder a su pregunta. El manual de optimización en particular es lo que busca, pero el manual de instrucciones también tiene los tiempos y las latencias para la mayoría de las principales líneas de CPU para instrucciones SIMD, ya que varían de un chip a otro.
En general, consideraría las ramas completas, así como la búsqueda de punteros (lista de enlaces de viajes, llamadas a funciones virtuales) como los mejores asesinos, pero los cpus x86 / x64 son muy buenos en ambos, en comparación con otras arquitecturas. Si alguna vez se transfiere a otra plataforma, verá cuánto problema pueden ser, si está escribiendo un código de alto rendimiento.
fuente