¿Cómo podría una CPU ejecutar más de una instrucción por ciclo de reloj?

La respuesta simple: las CPU son como fábricas, que tienen “máquinas” que realizan diferentes tareas forman una “línea de producción”. Hay un “ritmo” común, el reloj central, pero como hacen cosas diferentes, es posible que pueda enviar múltiples instrucciones en la línea que se manejarán en caminos separados a través de la fábrica al mismo tiempo.

La respuesta técnica: esto se llama paralelismo a nivel de instrucción. En el caso más simple, explota el hecho de que necesita realizar múltiples operaciones para ejecutar una sola instrucción, pero todas esas operaciones son manejadas por circuitos separados; esto da lugar a la tubería de instrucciones:

(imagen de wikipedia)

Por lo tanto, ha quintuplicado su rendimiento para el caso promedio: en lugar de una instrucción por 5 ciclos de reloj, ahora está generando el resultado de una instrucción completa cada 1 ciclo de reloj.

Entonces, a continuación, ¿qué pasa si tuviera múltiples instrucciones para buscar y decodificar circuitos? ¿También múltiples ALU, líneas de recuperación de memoria, etc.? Esto se llama CPU Superscalar. Puede combinar esto con la canalización e intentar que el número máximo de operaciones de un tipo se ejecute al mismo tiempo, al tiempo que se ejecutan todos los diferentes tipos de operaciones en un solo ciclo de reloj. Alternativamente, podría poner este paralelismo en manos del programador, que se denomina CPU de palabra de instrucción muy larga; esto le permite tener instrucciones únicas que realizan múltiples acciones.

Ahora ha pasado de ejecutar una instrucción por 5 ciclos de reloj a ejecutar varias instrucciones completas por 1 ciclo de reloj. En el mejor de los casos, la CPU se ilumina como un árbol de Navidad: cada unidad activa al mismo tiempo, todo el tiempo.

Pero en todos esos enfoques, hay un gran problema: ¿qué pasa si la siguiente instrucción depende de la respuesta de la primera? Por ejemplo, ¿qué sucede si necesita realizar algunas operaciones aritméticas para determinar de dónde vendrán los datos para la segunda instrucción, que es algo muy común? Ingrese la ejecución fuera de orden, donde la CPU intenta reorganizar su programa de una manera más eficiente sin cambiar su significado; si bien esta dependencia anterior es algo que no puede cambiar, puede usar su tiempo libre para trabajar en otras partes del programa que son independientes del primer cálculo.

Entonces, después de todo esto, prueba su CPU y descubre que el rendimiento no es tan bueno como se imaginaba. Todas sus tuberías están vacías, después de haber completado todo lo que pudieron hace mucho tiempo, esperando la decisión final de la memoria principal durante cientos de ciclos (70-80% de las lecturas de memoria se resolverán desde la memoria caché, que es mucho más rápido, pero aún así no instantáneo, especialmente si está en L2 o L3 (o ahora L4, implementado como eDRAM)). Desde el punto de vista de su circuito, la memoria principal es el correo postal, y podría estar esperando una respuesta simple de sí / no, a la que sabe que la respuesta probablemente será la misma que la última vez que preguntó (o en algún patrón específico que reconoces), sin embargo, estás sentado allí, sin hacer nada. Esto conduce a predictores de rama y ejecución especulativa, donde simplemente continúa ejecutando las rutas más probables o incluso las dos, y cuando llega la respuesta de la memoria principal, simplemente arroja el resultado que asumió la respuesta incorrecta y se queda con el otro. En realidad, tiene más que suficiente tiempo, por lo que realizar cálculos innecesarios es más eficiente que la alternativa.

Tenga en cuenta que todo lo anterior es invisible para el programador (al menos en las CPU modernas y en lenguajes de alto nivel). Para aumentar aún más el rendimiento de un chip, también puede hacer algo más: tener múltiples núcleos completos en un solo dado. Sin embargo, esto coloca la carga de explotar el paralelismo en el programador, y es una carga pesada. La gente ha estado tratando de crear un compilador que paraleliza automáticamente el código durante mucho tiempo y con un éxito variable, pero todavía no estamos en ninguno de los idiomas principales (pero nos estamos acercando bastante a algunos no convencionales).

Luego llegamos al Pentium MMX, que provocó la revolución más reciente con los procesadores SIMD (Single Instruction Multiple Data). MMX, SSE, AVX, 3D ¡Ahora! son todos coprocesadores de “vector corto” (en contraste con los procesadores de vector de supercomputadora anteriores, que procesaron miles de elementos en paralelo). Las GPU también son ejemplos de procesadores SIMD (Computación de propósito general en unidades de procesamiento de gráficos para el fenómeno relativamente reciente de usarlos como procesadores normales, en lugar de solo para gráficos 3D). La idea es tener muchas unidades lógicas aritméticas, para que pueda realizar operaciones en múltiples elementos de datos a la vez; esto no hace que el procesador sea mucho más complejo, porque todos comparten la misma lógica de control (no puede tener saltos condicionales) , pero acelera las proyecciones, las multiplicaciones escalares vectoriales y muchas otras tareas del álgebra lineal. Algunos compiladores de lenguaje modernos de alto nivel incluso cuentan con “auto-vectorización”, lo que significa que pueden encontrar código que procesa múltiples elementos de la misma manera y luego emiten instrucciones para el coprocesador de vectores, hasta hace poco, usando esas instrucciones requeridas desplegables para ensamblar dividir un programa entre la CPU y la GPU todavía no es automático (en la GPU, la parte “MD” de “SIMD” se lleva al extremo, lo que lo hace mucho más eficiente que cualquier CPU para algunas tareas específicas).

Grandes otras respuestas. Lo único que quiero agregar es que, si bien estas ideas parecen nuevas y modernas para muchas personas, en realidad se remontan a los primeros días de las computadoras.

Por ejemplo, el CDC 6600 se entregó por primera vez en 1965. Fue diseñado por Seymour Cray. Por lo tanto, no debería sorprendernos que tuviera muchas ideas muy innovadoras y fuera considerado el primer superordenador.

Podría funcionar a hasta 3 MIPS (Millones de instrucciones por segundo). Solo sonreímos ante eso hoy (un teléfono típico tiene un procesador que puede hacer de 1 a 15 mil MIPS); pero no hubo nada más rápido que esta bestia durante los próximos cinco años (y eso también fue de CDC y Cray).

Tenía múltiples unidades funcionales que podían ejecutar partes de instrucciones en paralelo. Tenía una pequeña caché de alta velocidad (por el momento) para las instrucciones y, en efecto, las unidades de función “libremente” se alinearon sobre las instrucciones en esa caché ejecutando partes de instrucciones fuera de orden cuando lo permitían las restricciones.

La CPU puede hacer esto si:

1. Las instrucciones se pueden ejecutar en diferentes partes de la CPU
2. La segunda instrucción no depende del resultado de la primera instrucción.

Por ejemplo, si mi primera instrucción es “agregar 3 al registro A” y mi segunda instrucción es “almacenar 15 en el registro B”, entonces probablemente pueda hacer esto simultáneamente. No necesito saber el valor final del registro A para ejecutar la tienda (condición 1) y agregar y almacenar probablemente use diferentes partes de la CPU (condición 2).

Tenga en cuenta que a veces puede agregar deliberadamente circuitos redundantes a la CPU para hacer que estas ejecuciones múltiples sean más probables. Si la CPU tenía dos sumadores, entonces puedo ejecutar dos instrucciones de agregar simultáneamente.

También hay otros trucos; puede tener dos instrucciones que dependen una de la otra, pero la siguiente instrucción que sigue no depende de ninguna de esas dos; en ese caso, incluso puede ejecutar las instrucciones sin orden sin cambiar el resultado. Esto sucede mucho también.

Vea computadoras con palabras de instrucción muy largas (VLIW). Se trata de tratar de usar todas las unidades funcionales dentro de la CPU al mismo tiempo. Hyperthreading (falsificar una segunda CPU para obtener una mejor utilización de las ALU) es otra técnica hacia el mismo objetivo.

Este problema surge de la vergüenza de la riqueza en el área de chips que nos da la Ley de Moore.

La lógica de emisión de instrucciones analizará las siguientes instrucciones simultáneamente y emitirá varias instrucciones en un solo ciclo si esas instrucciones son independientes.

Al tener más de una tubería.

Lea este interesante artículo para obtener más detalles:

http://www.lighterra.com/papers/