assembly cpu-speed

assembly - ¿Exactamente qué tan "rápidas" son las CPU modernas?



cpu-speed (14)

Cuando solía programar sistemas embebidos y computadoras tempranas de 8/16 bits (6502, 68K, 8086) tenía un buen manejo de cuánto tiempo (en nanosegundos o microsegundos) le tomó ejecutar cada instrucción. Dependiendo de la familia, uno (o cuatro) ciclos equivale a una "recuperación de memoria", y sin cachés de qué preocuparse, puede adivinar los tiempos en función de la cantidad de accesos de memoria implicados.

Pero con las CPU modernas, estoy confundido. Sé que son mucho más rápidos, pero también sé que la velocidad de los titulares de gigahercios no es útil sin saber cuántos ciclos de ese reloj se necesitan para cada instrucción.

Entonces, ¿alguien puede proporcionar algunos tiempos para dos instrucciones de muestra, en (digamos) un Core 2 Duo de 2GHz? Los mejores y peores casos (suponiendo que no haya nada en el caché / todo en el caché) serían útiles.

Instrucción n. ° 1: agregue un registro de 32 bits a un segundo.

Instrucción n. ° 2: mueva un valor de 32 bits de registro a memoria.

Editar : La razón por la que pregunto esto es para tratar de desarrollar una "regla empírica" ​​que me permita ver el código simple y medir aproximadamente el tiempo llevado al orden de magnitud más cercano.

Editar # 2: Muchas respuestas con puntos interesantes, pero nadie (todavía) ha presentado una cifra medida en el tiempo. Aprecio que haya "complicaciones" en la pregunta, pero vamos: si podemos estimar el número de sintonizadores de piano en Nueva York , deberíamos poder estimar los tiempos de ejecución del código ...

Tome el siguiente código (tonto):

int32 sum = frigged_value(); // start timing for (int i = 0 ; i < 10000; i++) { for (int j = 0 ; j < 10000; j++) { sum += (i * j) } sum = sum / 1000; } // end timing

¿Cómo podemos estimar cuánto tiempo llevará correr ... 1 femtosegundo? 1 gigayear?


Como ya señaló Doug, el mejor caso es cero (procesador superescalar, unidades de ejecución múltiples, datos que ya están en la memoria caché L1).

El peor de los casos es de hasta varios milisegundos (cuando el SO maneja una falla de página y tiene que buscar los datos / instrucciones del disco). Excluir disco / intercambio aún depende de si tiene una máquina NUMA, qué tipo de topología tiene, en qué nodo de memoria se encuentran los datos, si hay acceso concurrente desde otra CPU (bloqueo de bus y protocolos de sincronización de caché), etc.


Es casi imposible proporcionar información de tiempo precisa que está esperando de una manera que le resulte ÚTIL.

Los siguientes conceptos afectan el tiempo de instrucción; algunos pueden variar de momento a momento:

  • Descomposición de Micro-op
  • Operación de canalización
  • Ejecución súper escalar
  • Ejecución fuera de servicio
  • Ejecución SMT / SMP
  • Modo de punto flotante
  • Predicción de ramas / precarga
  • Latencia de caché
  • Latencia de memoria
  • Regulación de la velocidad del reloj
  • etc

Consulte un libro sobre la arquitectura de la computadora moderna si necesita alguna explicación adicional sobre los conceptos anteriores.

La mejor manera de medir la velocidad de su código es (¡sorpresa!) Medir la velocidad de su código ejecutando la misma carga de trabajo y en las mismas condiciones que esperaba cuando estaba "en el mundo real".


Los procesadores modernos como Core 2 Duo que mencionas son tanto superescalares como canalizados . Tienen múltiples unidades de ejecución por núcleo y en realidad están trabajando en más de una instrucción a la vez por núcleo; esta es la parte superescalar La parte canalizada significa que hay una latencia desde cuando se lee y se "emite" una instrucción hasta cuando se completa la ejecución y este tiempo varía dependiendo de las dependencias entre esa instrucción y las otras que se mueven a través de las otras unidades de ejecución al mismo tiempo. Entonces, en efecto, el tiempo de cualquier instrucción dada varía dependiendo de lo que está alrededor y de lo que depende. Esto significa que una instrucción dada tiene una especie de mejor caso y el peor tiempo de ejecución de casos se basa en una serie de factores. Debido a las múltiples unidades de ejecución, en realidad puede tener más de una instrucción completando la ejecución por reloj central, pero a veces hay varios relojes entre terminaciones si la tubería tiene que detenerse esperando memoria o dependencias en las tuberías.

Todo lo anterior es solo desde la vista del núcleo de la CPU. Luego tiene interacciones con los cachés y la contención del ancho de banda con los otros núcleos. La Unidad de Interfaz de Bus de la CPU trata de obtener instrucciones y datos que se introducen en el núcleo y que los resultados vuelvan a salir del núcleo a través de los cachés a la memoria.

Las reglas generales de orden de magnitud aproximadas deben tomarse con un grano de sal:

  • Las operaciones de Registrarse para Registrarse requieren 1 reloj central para ejecutarse. En general, esto debería ser conservador, especialmente porque más de estos aparecen en secuencia.
  • Las operaciones de carga y almacenamiento relacionadas con la memoria requieren 1 reloj de bus de memoria para ejecutarse. Esto debería ser muy conservador. Con una alta tasa de aciertos de caché será más como 2 relojes de bus de CPU que es la frecuencia de reloj del bus entre el núcleo de la CPU y la caché, pero no necesariamente el reloj del núcleo.

Los procesadores modernos hacen cosas aún más complicadas.

Ejecución fuera de orden. Si es posible hacerlo sin afectar el comportamiento correcto, los procesadores pueden ejecutar instrucciones en un orden diferente al que figura en su programa. Esto puede ocultar la latencia de las instrucciones de larga duración.

Registre el cambio de nombre. Los procesadores a menudo tienen más registros físicos que registros direccionables en su conjunto de instrucciones (los denominados registros "arquitectónicos"). Esto puede ser para compatibilidad con versiones anteriores o simplemente para habilitar codificaciones de instrucciones eficientes. A medida que se ejecuta un programa, el procesador "cambiará el nombre" de los registros de arquitectura que utiliza a los registros físicos que sean gratuitos. Esto permite que el procesador adquiera más paralelismo del que existía en el programa original.

Por ejemplo, si tiene una secuencia larga de operaciones en EAX y ECX, seguido de instrucciones que reinicializan EAX y ECX a valores nuevos y realiza otra secuencia larga de operaciones, el procesador puede usar diferentes registros físicos para ambas tareas y ejecutar ellos en paralelo.

La microarquitectura Intel P6 realiza tanto la ejecución fuera de servicio como el cambio de nombre de registro. La arquitectura Core 2 es la última derivada del P6.

Para responder realmente a su pregunta, es básicamente imposible para usted determinar el rendimiento a mano frente a todas estas optimizaciones arquitectónicas.


No es tan simple. El tiempo para sus dos instrucciones no le ayudará a medir mucho más el rendimiento de un conjunto de instrucciones más grande. Esto se debe a que los procesadores modernos pueden ejecutar muchas operaciones en paralelo y tener cachés grandes, por lo que "mover un valor a la memoria" ocurre en un momento bastante alejado de la ejecución de la instrucción.

Entonces, el mejor caso es cero (cuando se ejecuta en paralelo con otras instrucciones). Pero, ¿cómo te ayuda eso?

Esta página web muestra algunos puntos de referencia, incluidos algunos% de resultados de MIPS / MHz. Como puede ver, en muchos puntos de referencia hay múltiples instrucciones ejecutadas por ciclo de reloj. Los gráficos también muestran los efectos del tamaño de la memoria caché y la velocidad de la memoria.


Todo lo que necesita está en los manuales de CPU apropiados. Tanto AMD como Intel tienen PDF disponibles en su sitio web que describen las latencias de cada instrucción.

Solo tenga en cuenta la complejidad de las CPU modernas. No ejecutan una instrucción a la vez, pueden cargar de 3 a 4 instrucciones por ciclo, y casi todas las instrucciones se canalizan, de modo que cuando se cargan las siguientes instrucciones, las actuales no están casi terminadas. También reordena las instrucciones para permitir una programación más eficiente. Una CPU moderna puede tener fácilmente 50 instrucciones en progreso a la vez.

Entonces estás haciendo la pregunta incorrecta. El tiempo necesario para una sola instrucción varía enormemente dependiendo de cómo y cuándo se mida. Depende de cuán ocupado está el decodificador de instrucciones, del predictor de bifurcación, de la programación y de las otras instrucciones que se están programando, además de los problemas simples como el almacenamiento en caché.


Una cita interesante de Alan Kay en 2004 :

Solo como un lado, para darle un punto de referencia interesante, en aproximadamente el mismo sistema, aproximadamente optimizado de la misma manera, un punto de referencia desde 1979 en Xerox PARC solo funciona hoy 50 veces más rápido. La ley de Moore nos ha dado una mejora de entre 40,000 y 60,000 veces en ese momento. Así que hay aproximadamente un factor de 1000 en la eficiencia que se ha perdido por malas arquitecturas de CPU.

La implicación parece ser que las mejoras en el rendimiento de la CPU parecen enfocarse en áreas donde tienen relativamente poco impacto en el software que realmente escribimos.


Usando una descripción basada en gran medida en la arquitectura Intel Pentium, para resumir una historia muy larga:

  • el procesador tiene varias "unidades de ejecución" que pueden realizar diferentes tipos de ''microoperaciones''; las instrucciones se pueden dividir en varias microoperaciones
  • las diferentes unidades de ejecución esencialmente se ejecutan en paralelo
  • cada microoperador vincula la unidad de ejecución correspondiente para un cierto número de ciclos de reloj, por lo que ninguna otra instrucción puede usar esa unidad de ejecución: por ejemplo, "agregar punto flotante" puede atar la unidad "FP ejecutar" durante 2 ciclos de reloj
  • las unidades de ejecución se agrupan por "puerto", y cada ciclo de reloj, se puede enviar una nueva microoperación a cada puerto (suponiendo que la unidad de ejecución pertinente está libre en ese momento); algunas unidades también pueden recibir una "operación extra" a la mitad del ciclo; entonces cada ciclo de reloj, un cierto número de operaciones puede comenzar a ejecutarse;
  • el procesador puede reordenar microoperaciones donde esto no interrumpe las dependencias (o donde el resultado aún puede ser reconstruido) para aprovechar las unidades de ejecución que están libres en un momento dado
  • para que las instrucciones se puedan ejecutar en paralelo, pero qué partes de qué instrucciones se están ejecutando en un momento dado es una situación bastante compleja
  • el tiempo total para una instrucción determinada depende de cuánto tiempo tuvo que "esperar" para que las unidades de ejecución necesarias estuvieran disponibles, el tiempo real que esas operaciones pasaron ejecutándose en las unidades dadas, más cualquier tiempo adicional requerido para "atar el resultado"

Dado que el tiempo de una instrucción depende de las instrucciones que lo rodean, en la práctica, por lo general, es mejor programar una pieza representativa de código que tratar de preocuparse por las instrucciones individuales. Sin embargo:

  • Intel (y presumiblemente otros fabricantes) publican una lista de rendimiento de la instrucción y tiempos de latencia
  • el rendimiento es la cantidad de ciclos de reloj realmente necesarios en la (s) unidad (es) de ejecución relevante (s)
  • la latencia es un número de "peor caso" de ciclos de reloj requeridos, una vez que una instrucción comienza a ejecutarse, antes de que el resultado de esa ejecución esté disponible como entrada a otra instrucción

Entonces, por ejemplo, si, digamos, las instrucciones de suma y multiplicación de punto flotante tienen un rendimiento de 2 y una latencia de 5 (en realidad, para multiplicar es un poco mayor, creo), eso significa agregar un registro a sí mismo o multiplicarlo por en sí mismo probablemente tomará dos ciclos de reloj (ya que no hay otros valores dependientes), mientras que agregarlo como resultado de una multiplicación anterior tomará algo así como un poco menos de 2 + 5 ciclos de reloj, dependiendo de dónde inicie / termine el tiempo, y en todo tipo de otras cosas. (Durante algunos de esos ciclos de reloj, podría estar ocurriendo otra operación de agregar / multiplicar, por lo que es discutible la cantidad de ciclos que realmente atribuyes a las instrucciones de agregar / mezclar de todos modos ...)

Ah, y solo como un ejemplo concreto. Para seguir el código de Java

public void runTest(double[] data, double randomVal) { for (int i = data.length-1; i >= 0; i--) { data[i] = data[i] + randomVal; } }

Hotspot 1.6.12 JIT-compila la secuencia de bucle interno con el siguiente código Intel, que consiste en un load-add-store para cada posición en el conjunto (con ''randomVal'' siendo retenido en XMM0a en este caso):

0b3 MOVSD XMM1a,[EBP + #16] 0b8 ADDSD XMM1a,XMM0a 0bc MOVSD [EBP + #16],XMM1a 0c1 MOVSD XMM1a,[EBP + #8] 0c6 ADDSD XMM1a,XMM0a 0ca MOVSD [EBP + #8],XMM1a ...

cada grupo de load-add-store parece tomar 5 ciclos de reloj .


El tipo de predicción que estás pidiendo es inútil.

Si desea una regla general, aquí hay algunas reglas generales:

  • En el tiempo que lleva obtener una palabra de la memoria caché de nivel 2, un procesador puede ejecutar al menos 10 instrucciones. Así que preocúpate por el acceso a la memoria, no por las instrucciones: el cómputo en los registros es casi gratuito.

  • En el tiempo que lleva obtener una palabra de RAM, un procesador puede ejecutar miles de instrucciones (este número varía en un par de órdenes de magnitud dependiendo de los detalles de su hardware). Asegúrese de que esto ocurra solo en un caché frío; de lo contrario, nada más importa.

  • Si está ejecutando CPUs x86, no hay suficientes registros. Intenta no tener más de 5 variables activas en tu código en cualquier momento. O mejor aún, pase a AMD64 ( x86_64 ) y duplique la cantidad de registros. Con 16 registros y parámetros pasados ​​en registros, puede dejar de preocuparse por los registros.

Hubo un momento en el que cada año le preguntaba a un arquitecto qué reglas generales debería usar para predecir el costo del código generado por mis compiladores. Me detuve, porque la última vez que recibí una respuesta útil fue en 1999. (La respuesta fue "asegúrese de que sus bucles encajen en el búfer de reorden." Todos aquellos que saben qué es un búfer de reorden ahora pueden levantar sus manos. señala si puedes descubrir el tamaño del búfer de reorden en cualquier computadora que estés usando actualmente).


No creo que el peor de los casos esté limitado en algunas plataformas. Cuando tiene múltiples núcleos y procesadores compitiendo por las mismas ubicaciones o ubicaciones de memoria adyacentes, puede ver todo tipo de degradación en el rendimiento. Las líneas de caché deben moverse de un procesador a otro. No he visto un buen número de peor caso para las operaciones de memoria en las plataformas modernas.




Ya hay muchas buenas respuestas en este hilo, pero un tema no se menciona hasta el momento: la predicción errónea de las ramas .

Debido a que todos los procesadores modernos se canalizan, cuando el decodificador de instrucciones se encuentra con una instrucción como "saltar si es igual", no tiene idea de qué camino saltará la instrucción, y por lo tanto, simplemente adivina. Luego continúa alimentando las instrucciones en la tubería en función de esa suposición. Si hizo la predicción correcta, el thruput y la latencia de la instrucción de salto es esencialmente cero. Si se adivina incorrectamente, la interrupción y la latencia de la misma instrucción de salto podría ser de 50 o 100 ciclos.

Tenga en cuenta que la misma instrucción puede tener el "costo cero" la primera vez que se ejecuta en un ciclo y el costo realmente enorme la próxima vez que se ejecute la misma instrucción.


Esto solo responde a una parte de su pregunta, pero encontré útil esta tabla de Wikipedia en la localidad de referencia . Describe la velocidad de acceso y la cantidad de memoria en diferentes niveles de la jerarquía de la memoria, utilizando tiempos aproximados de 2006:

  • Registros de CPU (8-32 registros) - acceso inmediato (0-1 ciclos de reloj)
  • Cachés de CPU L1 (32 KiB a 128 KiB) - acceso rápido (3 ciclos de reloj)
  • Cachés de CPU L2 (128 KiB a 12 MiB): acceso un poco más lento (10 ciclos de reloj)
  • Memoria física principal (RAM) (256 MiB a 4 GiB) - acceso lento (100 ciclos de reloj)
  • Disco (sistema de archivos) (1 GiB a 1 TiB) - muy lento (10,000,000 ciclos de reloj)
  • Memoria remota (como otras computadoras o Internet) (Prácticamente ilimitada): la velocidad varía