Diseñar un procesador para ofrecer un alto rendimiento es mucho más que simplemente aumentar la velocidad del reloj. Existen muchas otras formas de aumentar el rendimiento, habilitadas a través de la ley de Moore e instrumentales para el diseño de procesadores modernos.
Las tasas de reloj no pueden aumentar indefinidamente.
A primera vista, puede parecer que un procesador simplemente ejecuta una secuencia de instrucciones una tras otra, con aumentos de rendimiento logrados a través de velocidades de reloj más altas. Sin embargo, aumentar la velocidad del reloj por sí solo no es suficiente. El consumo de energía y la producción de calor aumentan a medida que aumentan las velocidades de reloj.
Con velocidades de reloj muy altas, es necesario un aumento significativo en el voltaje del núcleo de la CPU . Debido a que el TDP aumenta con el cuadrado del núcleo V , eventualmente llegamos a un punto en el que el consumo excesivo de energía, la producción de calor y los requisitos de enfriamiento evitan mayores aumentos en la frecuencia de reloj. Este límite se alcanzó en 2004, en los días del Pentium 4 Prescott . Si bien las recientes mejoras en la eficiencia energética han ayudado, ya no son factibles aumentos significativos en la velocidad del reloj. Ver: ¿Por qué los fabricantes de CPU dejaron de aumentar las velocidades de reloj de sus procesadores?
Gráfico de velocidades de reloj estándar en PC entusiastas de vanguardia a lo largo de los años. Fuente de imagen
- A través de la ley de Moore , una observación que establece que el número de transistores en un circuito integrado se duplica cada 18 a 24 meses, principalmente como resultado de la reducción de los troqueles , se han implementado una variedad de técnicas que aumentan el rendimiento. Estas técnicas se han perfeccionado y perfeccionado a lo largo de los años, lo que permite ejecutar más instrucciones durante un período de tiempo determinado. Estas técnicas se analizan a continuación.
Las secuencias de instrucciones aparentemente secuenciales a menudo se pueden paralelizar.
- Aunque un programa puede consistir simplemente en una serie de instrucciones para ejecutarse una tras otra, estas instrucciones, o partes de las mismas, a menudo se pueden ejecutar simultáneamente. Esto se llama paralelismo de nivel de instrucción (ILP) . La explotación de ILP es vital para lograr un alto rendimiento, y los procesadores modernos utilizan numerosas técnicas para hacerlo.
La canalización divide las instrucciones en piezas más pequeñas que se pueden ejecutar en paralelo.
Cada instrucción se puede dividir en una secuencia de pasos, cada uno de los cuales es ejecutado por una parte separada del procesador. La canalización de instrucciones permite que varias instrucciones pasen por estos pasos una tras otra sin tener que esperar a que cada instrucción termine por completo. La canalización permite velocidades de reloj más altas: al completar un paso de cada instrucción en cada ciclo de reloj, se necesitaría menos tiempo para cada ciclo que si las instrucciones completas tuvieran que completarse de una en una.
La canalización RISC clásica contiene cinco etapas: búsqueda de instrucciones, decodificación de instrucciones, ejecución de instrucciones, acceso a memoria y reescritura. Los procesadores modernos dividen la ejecución en muchos más pasos, produciendo una tubería más profunda con más etapas (y aumentando las velocidades de reloj alcanzables ya que cada etapa es más pequeña y toma menos tiempo en completarse), pero este modelo debería proporcionar una comprensión básica de cómo funciona la canalización.
Fuente de imagen
Sin embargo, la canalización puede introducir riesgos que deben resolverse para garantizar la correcta ejecución del programa.
Debido a que se ejecutan diferentes partes de cada instrucción al mismo tiempo, es posible que ocurran conflictos que interfieran con la ejecución correcta. Estos se llaman peligros . Hay tres tipos de riesgos: datos, estructurales y de control.
Los riesgos de datos se producen cuando las instrucciones leen y modifican los mismos datos al mismo tiempo o en el orden incorrecto, lo que puede dar lugar a resultados incorrectos. Los riesgos estructurales ocurren cuando se necesitan múltiples instrucciones para usar una parte particular del procesador al mismo tiempo. Los riesgos de control ocurren cuando se encuentra una instrucción de ramificación condicional.
Estos peligros pueden resolverse de varias maneras. La solución más simple es simplemente detener la tubería, poniendo temporalmente la ejecución de una o instrucciones en la tubería en espera para garantizar resultados correctos. Esto se evita siempre que sea posible porque reduce el rendimiento. Para los riesgos de datos, se utilizan técnicas como el reenvío de operandos para reducir las paradas. Los riesgos de control se manejan a través de la predicción de ramas , que requiere un tratamiento especial y se trata en la siguiente sección.
La predicción de rama se utiliza para resolver los riesgos de control que pueden interrumpir toda la tubería.
Los riesgos de control, que ocurren cuando se encuentra una rama condicional , son particularmente graves. Las ramas introducen la posibilidad de que la ejecución continúe en otra parte del programa en lugar de simplemente la siguiente instrucción en la secuencia de instrucciones, en función de si una condición particular es verdadera o falsa.
Debido a que la siguiente instrucción a ejecutar no puede determinarse hasta que se evalúe la condición de la ramificación, no es posible insertar ninguna instrucción en la tubería después de una ramificación en ausencia. Por lo tanto, la tubería se vacía (se descarga ), lo que puede desperdiciar casi tantos ciclos de reloj como etapas en la tubería. Las ramas tienden a ocurrir muy a menudo en los programas, por lo que los riesgos de control pueden afectar gravemente el rendimiento del procesador.
La predicción de rama aborda este problema al adivinar si se tomará una rama. La forma más sencilla de hacer esto es simplemente asumir que las ramas siempre se toman o nunca se toman. Sin embargo, los procesadores modernos utilizan técnicas mucho más sofisticadas para una mayor precisión de predicción. En esencia, el procesador realiza un seguimiento de las ramas anteriores y utiliza esta información de varias maneras para predecir la próxima instrucción a ejecutar. La tubería se puede alimentar con instrucciones de la ubicación correcta en función de la predicción.
Por supuesto, si la predicción es incorrecta, las instrucciones que se pusieron a través de la tubería después de que se haya eliminado la rama, enjuagando la tubería. Como resultado, la precisión del predictor de rama se vuelve cada vez más crítica a medida que las tuberías se hacen cada vez más largas. Las técnicas específicas de predicción de ramas están más allá del alcance de esta respuesta.
Los cachés se utilizan para acelerar los accesos a la memoria.
Los procesadores modernos pueden ejecutar instrucciones y procesar datos mucho más rápido de lo que se puede acceder en la memoria principal. Cuando el procesador debe acceder a la RAM, la ejecución puede detenerse durante largos períodos de tiempo hasta que los datos estén disponibles. Para mitigar este efecto, se incluyen pequeñas áreas de memoria de alta velocidad llamadas cachés en el procesador.
Debido al espacio limitado disponible en la matriz del procesador, los cachés tienen un tamaño muy limitado. Para aprovechar al máximo esta capacidad limitada, los cachés almacenan solo los datos a los que se ha accedido más recientemente o con mayor frecuencia ( localidad temporal ). Como los accesos a la memoria tienden a agruparse dentro de áreas particulares ( localidad espacial ), los bloques de datos cerca de lo que se accedió recientemente también se almacenan en la memoria caché. Ver: localidad de referencia
Los cachés también se organizan en múltiples niveles de tamaño variable para optimizar el rendimiento, ya que los cachés más grandes tienden a ser más lentos que los más pequeños. Por ejemplo, un procesador puede tener un caché de nivel 1 (L1) que solo tiene un tamaño de 32 KB, mientras que su caché de nivel 3 (L3) puede tener varios megabytes de tamaño. El tamaño de la memoria caché, así como la asociatividad de la memoria caché, que afecta la forma en que el procesador gestiona la sustitución de datos en una memoria caché completa, afecta significativamente las ganancias de rendimiento que se obtienen a través de una memoria caché.
La ejecución fuera de orden reduce las paradas debido a los peligros al permitir que las instrucciones independientes se ejecuten primero.
No todas las instrucciones en un flujo de instrucciones dependen unas de otras. Por ejemplo, aunque a + b = c
es posible antes c + d = e
, a + b = c
y d + e = f
son independientes y se pueden ejecutar al mismo tiempo.
La ejecución fuera de orden aprovecha este hecho para permitir que otras instrucciones independientes se ejecuten mientras una instrucción está detenida. En lugar de requerir que las instrucciones se ejecuten una tras otra,se agrega hardware de programación para permitir que se ejecuten instrucciones independientes en cualquier orden. Las instrucciones son enviados a una cola de instrucción y emitidas a la parte apropiada del procesador cuando los datos necesarios estén disponibles. De esa manera, las instrucciones que están atascadas esperando los datos de una instrucción anterior no vinculan las instrucciones posteriores que son independientes.
Fuente de imagen
- Se requieren varias estructuras de datos nuevas y ampliadas para realizar la ejecución fuera de orden. La cola de instrucciones antes mencionada, la estación de reserva , se utiliza para retener instrucciones hasta que los datos necesarios para la ejecución estén disponibles. El búfer de reordenamiento (ROB) se utiliza para realizar un seguimiento del estado de las instrucciones en curso, en el orden en que se recibieron, de modo que las instrucciones se completen en el orden correcto. Se necesita un archivo de registro que se extienda más allá del número de registros proporcionados por la propia arquitectura para cambiar el nombre del registro , lo que ayuda a evitar que las instrucciones independientes se vuelvan dependientes debido a la necesidad de compartir el conjunto limitado de registros proporcionados por la arquitectura.
Las arquitecturas superescalares permiten que se ejecuten varias instrucciones dentro de un flujo de instrucciones al mismo tiempo.
Las técnicas discutidas anteriormente solo aumentan el rendimiento de la canalización de instrucciones. Estas técnicas por sí solas no permiten completar más de una instrucción por ciclo de reloj. Sin embargo, a menudo es posible ejecutar instrucciones individuales dentro de un flujo de instrucciones en paralelo, como cuando no dependen unas de otras (como se discutió en la sección de ejecución fuera de orden anterior).
Las arquitecturas superescalares aprovechan este paralelismo de nivel de instrucción al permitir que se envíen instrucciones a múltiples unidades funcionales a la vez. El procesador puede tener múltiples unidades funcionales de un tipo particular (como ALU enteras) y / o diferentes tipos de unidades funcionales (como unidades de punto flotante y enteras) a las que se pueden enviar simultáneamente instrucciones.
En un procesador superescalar, las instrucciones se programan como en un diseño fuera de servicio, pero ahora hay múltiples puertos de emisión , lo que permite emitir y ejecutar diferentes instrucciones al mismo tiempo. La circuitería de decodificación de instrucciones ampliada permite al procesador leer varias instrucciones a la vez en cada ciclo de reloj y determinar las relaciones entre ellas. Un procesador moderno de alto rendimiento puede programar hasta ocho instrucciones por ciclo de reloj, dependiendo de lo que haga cada instrucción. Así es como los procesadores pueden completar múltiples instrucciones por ciclo de reloj. Ver: motor de ejecución de Haswell en AnandTech
Fuente de imagen
- Sin embargo, las arquitecturas superescalares son muy difíciles de diseñar y optimizar. La comprobación de dependencias entre instrucciones requiere una lógica muy compleja cuyo tamaño puede escalar exponencialmente a medida que aumenta el número de instrucciones simultáneas. Además, dependiendo de la aplicación, solo hay un número limitado de instrucciones dentro de cada flujo de instrucciones que se pueden ejecutar al mismo tiempo, por lo que los esfuerzos para aprovechar más el ILP sufren rendimientos decrecientes.
Se agregan instrucciones más avanzadas que realizan operaciones complejas en menos tiempo.
A medida que aumentan los presupuestos de los transistores, es posible implementar instrucciones más avanzadas que permitan realizar operaciones complejas en una fracción del tiempo que de otro modo tomarían. Los ejemplos incluyen conjuntos de instrucciones de vectores como SSE y AVX que realizan cálculos en múltiples piezas de datos al mismo tiempo y el conjunto de instrucciones AES que acelera el cifrado y descifrado de datos.
Para realizar estas operaciones complejas, los procesadores modernos usan microoperaciones (μops) . Las instrucciones complejas se decodifican en secuencias de μops, que se almacenan dentro de un búfer dedicado y se programan para su ejecución individualmente (en la medida permitida por las dependencias de datos). Esto proporciona más espacio al procesador para explotar ILP. Para mejorar aún más el rendimiento, se puede usar un caché μop especial para almacenar μops recientemente decodificados, de modo que los μops para instrucciones ejecutadas recientemente se puedan buscar rápidamente.
Sin embargo, la adición de estas instrucciones no aumenta automáticamente el rendimiento. Las nuevas instrucciones pueden aumentar el rendimiento solo si se escribe una aplicación para usarlas. La adopción de estas instrucciones se ve obstaculizada por el hecho de que las aplicaciones que las usan no funcionarán en procesadores más antiguos que no las admiten.
Entonces, ¿cómo mejoran estas técnicas el rendimiento del procesador con el tiempo?
Las tuberías se han alargado a lo largo de los años, lo que reduce la cantidad de tiempo necesario para completar cada etapa y, por lo tanto, permite velocidades de reloj más altas. Sin embargo, entre otras cosas, las tuberías más largas aumentan la penalización por una predicción de rama incorrecta, por lo que una tubería no puede ser demasiado larga. Al tratar de alcanzar velocidades de reloj muy altas, el procesador Pentium 4 utilizó tuberías muy largas, hasta 31 etapas en Prescott . Para reducir los déficits de rendimiento, el procesador intentaría ejecutar las instrucciones incluso si pudieran fallar, y seguiría intentándolo hasta que lo lograran . Esto condujo a un consumo de energía muy alto y redujo el rendimiento obtenido de la hiperprocesamiento . Los procesadores más nuevos ya no usan tuberías por tanto tiempo, especialmente porque la escala de la frecuencia de reloj ha llegado a una pared;Haswell usa una tubería que varía entre 14 y 19 etapas, y las arquitecturas de menor potencia usan tuberías más cortas (Intel Atom Silvermont tiene 12 a 14 etapas).
La precisión de la predicción de bifurcación ha mejorado con arquitecturas más avanzadas, reduciendo la frecuencia de descargas de tuberías causadas por predicciones erróneas y permitiendo que se ejecuten más instrucciones simultáneamente. Teniendo en cuenta la longitud de las tuberías en los procesadores actuales, esto es fundamental para mantener un alto rendimiento.
Con el aumento de los presupuestos de transistores, se pueden incrustar cachés más grandes y más efectivas en el procesador, reduciendo las paradas debido al acceso a la memoria. Los accesos a la memoria pueden requerir más de 200 ciclos para completarse en los sistemas modernos, por lo que es importante reducir la necesidad de acceder a la memoria principal tanto como sea posible.
Los procesadores más nuevos pueden aprovechar mejor ILP a través de una lógica de ejecución superescalar más avanzada y diseños "más amplios" que permiten decodificar y ejecutar más instrucciones simultáneamente. La arquitectura Haswell puede decodificar cuatro instrucciones y despachar 8 microoperaciones por ciclo de reloj. Los presupuestos de transistores crecientes permiten que se incluyan más unidades funcionales como ALU enteras en el núcleo del procesador. Las estructuras de datos clave utilizadas en la ejecución fuera de orden y superescalar, como la estación de reserva, el búfer de reordenamiento y el archivo de registro, se expanden en diseños más nuevos, lo que permite al procesador buscar una ventana más amplia de instrucciones para explotar su ILP. Esta es una importante fuerza impulsora detrás de los aumentos de rendimiento en los procesadores de hoy.
Se incluyen instrucciones más complejas en los procesadores más nuevos, y un número creciente de aplicaciones utilizan estas instrucciones para mejorar el rendimiento. Los avances en la tecnología de compilación, incluidas las mejoras en la selección de instrucciones y la vectorización automática , permiten un uso más efectivo de estas instrucciones.
Además de lo anterior, una mayor integración de partes anteriormente externas a la CPU, como el puente norte, el controlador de memoria y los carriles PCIe reducen la E / S y la latencia de memoria. Esto aumenta el rendimiento al reducir las paradas causadas por demoras en el acceso a los datos de otros dispositivos.