Saltar al contenido

Ejecución fuera de orden frente a ejecución especulativa

Luego de de esta prolongada compilación de información hemos podido resolver esta interrogante que tienen algunos de nuestros lectores. Te dejamos la respuesta y nuestro objetivo es serte de mucha ayuda.

Solución:

La ejecución especulativa y la ejecución fuera de orden son ortogonales. Se podría diseñar un procesador así de OoO, pero no especulativo ni especulativo, sino en orden. La ejecución OoO es un modelo de ejecución en el que las instrucciones se pueden ejecutar en un orden que es potencialmente diferente del orden del programa. Sin embargo, las instrucciones aún se retiran en el orden del programa para que el comportamiento observado del programa sea el mismo que el esperado intuitivamente por el programador. (Aunque es posible diseñar un procesador OoO que retire instrucciones en un orden no natural con ciertas restricciones. Vea el estudio basado en simulación sobre esta idea: Maximización de recursos limitados: un estudio basado en límites y taxonomía de compromiso fuera de orden).

La ejecución especulativa, en términos generales, es un modelo de ejecución en el que las instrucciones se pueden recuperar y entrar en la canalización e incluso comenzar la ejecución sin siquiera saber con certeza que de hecho será necesario ejecutarlas (de acuerdo con el flujo de control del programa). El término también se usa a menudo para referirse específicamente a la ejecución especulativa en la etapa de ejecución del oleoducto. El documento Meltdown define estos términos en la página 3:

En este artículo, nos referimos a la ejecución especulativa en un significado más restringido, donde se refiere a una secuencia de instrucciones que sigue a una rama, y ​​usamos el término ejecución fuera de orden para referirnos a cualquier forma de hacer que se ejecute una operación antes de que el procesador haya comprometido los resultados de todas las instrucciones anteriores.

Tenga en cuenta que las instrucciones se pueden ejecutar de forma especulativa, pero en orden. Cuando la etapa de decodificación de la canalización identifica una instrucción de bifurcación condicional, puede especular sobre la bifurcación y su objetivo y obtener instrucciones de la ubicación objetivo prevista. Pero aún así, las instrucciones también se pueden ejecutar en orden. Sin embargo, tenga en cuenta que una vez que la instrucción de bifurcación condicional especulada y las instrucciones obtenidas de la ruta predicha (o ambas rutas) alcancen la etapa de emisión, ninguna de ellas se emitirá hasta que se retiren todas las instrucciones anteriores. Cuando eso suceda, el procesador sabría si la predicción fue correcta y, de lo contrario, limpiaría la tubería.

Los procesadores diseñados para realizar tareas simples y utilizados en sistemas integrados o dispositivos IoT no suelen ser especulativos ni OoO. Los procesadores de escritorio y de servidor son especulativos y OoO. En medio del espectro informático (teléfonos móviles y microcontroladores), puede encontrar procesadores que son OoO, pero no especulativos (como el ARM Cortex-A9). La microarquitectura de Intel Bonnell es especulativa, pero en orden. La ejecución especulativa es particularmente beneficiosa cuando se usa con OoO.

La confusión se produjo cuando leí los artículos de Meltdown y Spectre e hice una investigación adicional. En el artículo de Meltdown se indica que Meltdown se basa en una ejecución fuera de orden, mientras que algunos otros recursos, incluida la página wiki sobre ejecución separada, afirman que Meltdown se basa en una ejecución especulativa.

La vulnerabilidad Meltdown como se describe en el documento requiere una ejecución especulativa y fuera de orden. Sin embargo, esta es una declaración un tanto vaga, ya que hay muchas implementaciones de ejecución especulativas y fuera de orden. Meltdown no funciona con cualquier tipo de ejecución OoO o especulativa. Por ejemplo, ARM11 (utilizado en Raspberry Pis) admite algunas ejecuciones OoO limitadas y especulativas, pero no es vulnerable.

Vea la respuesta de Peter para obtener más detalles sobre Meltdown y su otra respuesta.

Relacionado: ¿Cuál es la diferencia entre la ejecución Superscalar y OoO ?.

Todavía me cuesta entender cómo Meltdown usa la ejecución especulativa. El ejemplo en el documento (el mismo que mencioné aquí anteriormente) usa IMO solo OoO – @Name en un comentario

Meltdown se basa en las CPU de Intel de manera optimista especulando que las cargas no fallarán, y que si una carga que falla llega a los puertos de carga, es el resultado de una rama anterior mal predicha. Entonces, la carga uop se marca, por lo que fallará si llega a la jubilación, pero la ejecución continúa especulativamente. usando datos, la entrada de la tabla de páginas dice que no se le permite leer desde el espacio de usuario.

En lugar de desencadenar una costosa recuperación de excepción cuando se ejecuta la carga, espera hasta que definitivamente se retira, porque esa es una forma barata de que la maquinaria maneje la falla de rama -> caso de carga defectuosa. En hardware, es más fácil para la tubería mantener la tubería a menos que usted necesitar que se detenga / bloquee para corregirlo. Por ejemplo, una carga en la que no hay ninguna entrada en la tabla de páginas y, por lo tanto, una TLB falla, tiene que esperar. Pero esperando incluso en un TLB pegar (para una entrada con permisos que bloquean su uso) se agregaría complejidad. Normalmente, una falla de página solo se genera después de una caminata de página fallida (que no encuentra una entrada para la dirección virtual), o al retirar una carga o tienda que no cumplió con los permisos de la entrada de TLB que alcanzó.

En una CPU moderna con canalización OoO, todos las instrucciones se tratan como especulativas hasta la jubilación. Solo en el momento de la jubilación las instrucciones se vuelven no especulativas. La maquinaria fuera de servicio realmente no sabe ni le importa si está especulando sobre un lado de una rama que se predijo pero aún no se ejecutó, o especulando sobre cargas pasadas potencialmente con fallas. “Especular” que las cargas no fallan o que las instrucciones ALU no generan excepciones ocurre incluso en CPU que en realidad no se consideran especulativas, pero la ejecución completamente fuera de orden convierte eso en otro tipo de especulación.

No me preocupa demasiado una definición exacta de “ejecución especulativa” y lo que cuenta y lo que no. Estoy más interesado en cómo funcionan realmente los diseños modernos fuera de orden, y que en realidad es más simple ni siquiera intentar distinguir lo especulativo de lo no especulativo hasta el final del proceso. Esta respuesta ni siquiera está tratando de abordar tuberías en orden más simples con búsqueda de instrucciones especulativas (basado en la predicción de rama) pero sin ejecución, o en cualquier lugar entre eso y el algoritmo completo de Tomasulo con un programador ROB + con OoO exec + en -Orden de jubilación para excepciones precisas.

Por ejemplo, solo después El retiro puede una tienda comprometerse desde el búfer de la tienda a la caché L1d, no antes. Y para absorber ráfagas cortas y fallas de caché, tampoco tiene que suceder como parte de la jubilación. Así que una de las únicas cosas fuera de orden no especulativas es comprometer las tiendas con L1d; definitivamente han sucedido en lo que respecta al estado arquitectónico, por lo que deben completarse incluso si ocurre una interrupción / excepción.

El mecanismo de falla si se alcanza la jubilación es una buena manera de evitar un trabajo costoso a la sombra de un error de predicción de la sucursal. También le da a la CPU el estado arquitectónico correcto (valores de registro, etc.) si la excepción se activa. Lo necesita, ya sea que permita o no que la maquinaria OoO siga funcionando con instrucciones más allá del punto en el que haya detectado una excepción.


Las faltas de rama son especiales: hay búferes que registran micro-estado arquitectónico (como registro-asignación) en las sucursales, por lo que la recuperación de sucursales puede retroceder a eso en lugar de vaciar la tubería y reiniciar desde el último estado de retiro bueno conocido. Las sucursales predicen erróneamente una cantidad considerable en código real. Otras excepciones son muy raras.

Las CPU modernas de alto rendimiento pueden mantener (fuera de servicio) la ejecución de uops antes de que se pierda una rama, mientras que descartan los uops y los resultados de ejecución posteriores a ese punto. La recuperación rápida es mucho más barata que descartar y reiniciar todo desde un estado de retiro que está potencialmente muy por detrás del punto en el que se descubrió el error de predicción.

Por ejemplo, en un bucle, las instrucciones que manejan el contador del bucle pueden adelantarse mucho al resto del cuerpo del bucle y detectar el error de predicción al final lo suficientemente pronto como para redirigir el front-end y tal vez no perder mucho rendimiento real, especialmente si el El cuello de botella era la latencia de una cadena de dependencia o algo diferente al rendimiento de uop.

Este mecanismo de recuperación optimizado solo se utiliza para las ramas (porque los búferes de instantáneas de estado son limitados), por lo que los fallos de rama son relativamente baratos en comparación con los vaciados de canalización completos. (p. ej., en Intel, la máquina de pedidos de memoria se borra, el contador de rendimiento machine_clears.memory_ordering: ¿Cuáles son los costos de latencia y rendimiento del intercambio entre productores y consumidores de una ubicación de memoria entre hiperhermanos versus no hiperhermanos?)


Sin embargo, las excepciones no son desconocidas; las fallas de página ocurren en el curso normal de operación. por ejemplo, almacenar en una página de solo lectura activa la copia al escribir. Cargar o almacenar en una página sin asignar activa la entrada de página o el manejo de la asignación diferida. Pero, por lo general, se ejecutan miles o millones de instrucciones entre cada falla de página, incluso en un proceso que asigna nueva memoria con frecuencia. (1 por micro o milisegundo en una CPU de 1 GHz). En el código que no asigna nueva memoria, puede ir mucho más tiempo sin excepciones. En su mayoría, solo un temporizador se interrumpe ocasionalmente en el procesamiento de números puro sin E / S.

Pero de todos modos, no desea activar una descarga de tubería ni nada costoso hasta que esté seguro que una excepción realmente se disparará. Y que estás seguro de que tienes Derecha excepción. por ejemplo, tal vez la dirección de carga para una carga con fallas anterior no estaba lista tan pronto, por lo que la primera carga con fallas que se ejecutó no fue la primera en el orden del programa. Esperar hasta la jubilación es una forma barata de obtener excepciones precisas. Barato en términos de transistores adicionales para manejar este caso, y permite que la maquinaria de retiro en orden habitual descubra exactamente qué excepción se dispara rápidamente.

El trabajo inútil realizado al ejecutar instrucciones después de una instrucción marcada como defectuosa en la jubilación cuesta un poquito de energía y no vale la pena bloquearlo porque las excepciones son muy raras.

Esto explica por qué tiene sentido diseñar hardware que fuera vulnerable a Meltdown en primer lugar. Obviamente es no seguro seguir haciendo esto, ahora que se ha pensado en Meltdown.


Arreglando Meltdown a bajo precio

No necesitamos bloquear la ejecución especulativa después de una carga defectuosa; solo debemos asegurarnos de que no utilice datos confidenciales. El problema no es que la carga tenga éxito especulativo, Meltdown se basa en las siguientes instrucciones que utilizan esos datos para producir efectos de microarquitectura dependientes de los datos. (por ejemplo, tocar una línea de caché en función de los datos).

Entonces, si los puertos de carga enmascarar los datos cargados a cero o algo así, así como configurar el indicador de falla en el retiro, la ejecución continúa pero no puede obtener información sobre los datos secretos. Esto debería tomar aproximadamente 1 retraso de puerta adicional de la ruta crítica, lo que probablemente sea posible en los puertos de carga sin limitar la velocidad del reloj o agregar un ciclo adicional de latencia. (1 ciclo de reloj es lo suficientemente largo para que la lógica se propague a través de muchas puertas Y / O dentro de una etapa de canalización, por ejemplo, un sumador completo de 64 bits).

Relacionado: sugerí el mismo mecanismo para una corrección de HW para Meltdown en ¿Por qué los procesadores AMD no son / menos vulnerables a Meltdown y Spectre ?.

Si guardas alguna vacilación o forma de innovar nuestro post eres capaz de realizar una acotación y con deseo lo ojearemos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *