¿Por qué un movimiento condicional no es vulnerable a un Fallo en la Predicción de Ramas?


Después de leer este post (respuesta en StackOverflow) (en la sección de optimización), me preguntaba por qué los movimientos condicionales no son vulnerables a un Fallo en la Predicción de ramas. He encontrado en un artículo sobre cond se mueve aquí (PDF por AMD). También allí, reclaman la ventaja de rendimiento de cond. mover. ¿Pero por qué es esto? No lo veo. En el momento en que esa instrucción ASM es evaluada, el resultado de la instrucción CMP anterior aún no se conoce.

Gracias.

Author: Community, 2013-01-03

5 answers

Las ramas mal predichas son caras

Un procesador moderno generalmente ejecuta entre una y tres instrucciones cada ciclo si las cosas van bien (si no se detiene esperando dependencias de datos para que estas instrucciones lleguen de instrucciones anteriores o de la memoria).

La instrucción anterior se mantiene sorprendentemente bien para bucles estrechos, pero esto no debería cegarle a una dependencia adicional que puede evitar que una instrucción se ejecute cuando su ciclo llegue: para una instrucción para ser ejecutada, el procesador debe haber comenzado a buscarla y decodificarla 15-20 ciclos antes.

¿Qué debe hacer el procesador cuando se encuentra con una rama? La obtención y decodificación de ambos destinos no se escalan (si siguen más ramas, un número exponencial de rutas tendría que ser obtenido en paralelo). Así que el procesador solo obtiene y decodifica una de las dos ramas, especulativamente.

Esta es la razón por la que las ramas mal predichas son caras: cuestan los 15-20 ciclos que son generalmente invisible debido a una tubería de instrucción eficiente.

El movimiento condicional nunca es muy caro

El movimiento condicional no requiere predicción, por lo que nunca puede tener esta penalización. Tiene dependencias de datos, igual que las instrucciones ordinarias. De hecho, un movimiento condicional tiene más dependencias de datos que las instrucciones ordinarias, porque las dependencias de datos incluyen casos de "condición verdadera" y "condición falsa". Después de una instrucción que condicionalmente se mueve r1 a r2, el contenido de r2 parece depender tanto del valor anterior de r2 como de r1. Una rama condicional bien predicha permite al procesador inferir dependencias más precisas. Pero las dependencias de datos suelen tardar uno o dos ciclos en llegar, si es que necesitan tiempo para llegar.

Tenga en cuenta que un movimiento condicional de la memoria al registro a veces sería una apuesta peligrosa: si la condición es tal que el valor leído desde la memoria no se asigna al registro, ha esperado en la memoria por nada. Pero las instrucciones de movimiento condicionales que se ofrecen en los conjuntos de instrucciones son típicamente register to register, evitando este error por parte del programador.

 60
Author: Pascal Cuoq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-03 00:28:35

Se trata de la tubería de instrucciones . Recuerde, las CPU modernas ejecutan sus instrucciones en una canalización, lo que produce un aumento significativo del rendimiento cuando el flujo de ejecución es predecible por la CPU.

Cmov

    add     eax, ebx
    cmp     eax, 0x10
    cmovne  ebx, ecx
    add     eax, ecx

En el momento en que esa instrucción ASM es evaluada, el resultado de la instrucción CMP anterior aún no se conoce.

Quizás, pero la CPU aún sabe que la instrucción que sigue a cmov se ejecutará inmediatamente después, independientemente del resultado de la instrucción cmp y cmov. Por lo tanto, la siguiente instrucción se puede recuperar/decodificar con seguridad antes de tiempo, lo que no es el caso con las ramas.

La siguiente instrucción podría incluso ejecutarse antes de que lo haga cmov (en mi ejemplo esto sería seguro)

Branch

    add     eax, ebx
    cmp     eax, 0x10
    je      .skip
    mov     ebx, ecx
.skip:
    add     eax, ecx

En este caso, cuando el decodificador de la CPU vea je .skip tendrá que elegir si continuar prefetching / decoding instrucciones ya sea 1) de la siguiente instrucción, o 2) desde el objetivo de salto. La CPU adivinará que esta rama condicional no sucederá, por lo que la siguiente instrucción mov ebx, ecx irá a la canalización.

Un par de ciclos más tarde, se ejecuta el je .skip y se toma la rama. Oh, mierda! Nuestro pipeline ahora contiene basura aleatoria que nunca debería ser ejecutada. La CPU tiene que vaciar todas sus instrucciones en caché y empezar de nuevo desde .skip:.

Esa es la penalización de rendimiento de ramas mal interpretadas, que nunca puede suceder con cmov ya que no altera el flujo de ejecución.

 41
Author: Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-19 19:56:33

De hecho, el resultado puede aún no ser conocido, pero si otras circunstancias lo permiten (en particular, la cadena de dependencias) la cpu puede reordenar y ejecutar instrucciones siguiendo el cmov. Dado que no hay ramificación, esas instrucciones deben evaluarse en cualquier caso.

Considere este ejemplo:

cmoveq edx, eax
add ecx, ebx
mov eax, [ecx]

Las dos instrucciones que siguen a cmov no dependen del resultado de la cmov, por lo que pueden ejecutarse incluso mientras la cmov está pendiente (esto se llama ejecución fuera de orden ). Incluso si no se pueden ejecutar, todavía se pueden recuperar y decodificar.

Una versión ramificada podría ser:

    jne skip
    mov edx, eax
skip:
    add ecx, ebx
    mov eax, [ecx]

El problema aquí es que el flujo de control está cambiando y la cpu no es lo suficientemente inteligente como para ver que podría simplemente "insertar" la instrucción omitida mov si la rama se malinterpreta como tomada - en su lugar, tira todo lo que hizo después de la rama, y se reinicia desde cero. Aquí es donde viene el castigo.

 15
Author: Jester,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-03 00:20:12

Usted debe leer estos. Con Fog + Intel, simplemente busque CMOV.

La crítica de Linus Torvald a CMOV circa 2007
Comparación de las microarquitecturas de Agner Fog
Manual de referencia de Optimización de arquitecturas Intel® 64 e IA-32

Respuesta corta, las predicciones correctas son 'gratis', mientras que los errores de interpretación condicionales pueden costar 14-20 ciclos en Haswell. Sin embargo, CMOV nunca es libre. Aún así creo que CMOV es mucho mejor ahora que cuando Torvalds despotricó. No hay una sola respuesta correcta para todos los tiempos en todos los procesadores.

 2
Author: Olsonist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-22 05:40:59

Tengo esta ilustración de [Peter Puschner et al.] diapositiva que explica cómo se transforma en código de ruta única, y acelerar la ejecución.

introduzca la descripción de la imagen aquí

 0
Author: COLD ICE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-27 10:35:28