En el nivel más bajo (en el hardware), sí, si los s son caros. Para comprender por qué, debe comprender cómo funcionan las tuberías .
La instrucción actual que se va a ejecutar se almacena en algo que normalmente se denomina puntero de instrucción (IP) o contador de programa (PC); estos términos son sinónimos, pero se utilizan términos diferentes con arquitecturas diferentes. Para la mayoría de las instrucciones, la PC de la siguiente instrucción es solo la PC actual más la longitud de la instrucción actual. Para la mayoría de las arquitecturas RISC, las instrucciones tienen una longitud constante, por lo que la PC se puede incrementar en una cantidad constante. Para arquitecturas CISC como x86, las instrucciones pueden ser de longitud variable, por lo que la lógica que decodifica la instrucción tiene que determinar cuánto tiempo es la instrucción actual para encontrar la ubicación de la siguiente instrucción.
Para la rama instrucciones, sin embargo, la siguiente instrucción a ejecutar no es la siguiente ubicación después de la instrucción en curso. Las ramas son gotos: le dicen al procesador dónde está la siguiente instrucción. Las ramas pueden ser condicionales o incondicionales, y la ubicación de destino puede ser fija o calculada.
Condicional versus incondicional es fácil de entender: una rama condicional solo se toma si se cumple una determinada condición (como si un número es igual a otro); si no se toma la rama, el control pasa a la siguiente instrucción después de la rama como de costumbre. Para las ramas incondicionales, la rama siempre se toma. Las ramas condicionales aparecen en if
declaraciones y las pruebas de control de for
y while
bucles. Las ramas incondicionales aparecen en ciclos infinitos, llamadas a funciones, retornos de funciones break
y continue
declaraciones, la goto
declaración infame y muchas más (estas listas están lejos de ser exhaustivas).
El objetivo de la sucursal es otro tema importante. La mayoría de las sucursales tienen un destino de sucursal fijo: van a una ubicación específica en el código que se fija en el momento de la compilación. Esto incluye if
declaraciones, bucles de todo tipo, llamadas a funciones regulares y muchos más. Las ramas calculadas calculan el destino de la rama en tiempo de ejecución. Esto incluye switch
declaraciones (a veces), retorno de una función, llamadas de función virtual y llamadas de puntero de función.
Entonces, ¿qué significa todo esto para el rendimiento? Cuando el procesador ve aparecer una instrucción de bifurcación en su canalización, necesita averiguar cómo continuar llenando su canalización. Para averiguar qué instrucciones vienen después de la rama en la secuencia del programa, necesita saber dos cosas: (1) si se tomará la rama y (2) el destino de la rama. Descubrir esto se llama predicción de rama y es un problema desafiante. Si el procesador adivina correctamente, el programa continúa a toda velocidad. Si, en cambio, el procesador adivina incorrectamente , simplemente pasó un tiempo calculando lo incorrecto. Ahora tiene que vaciar su tubería y volver a cargarla con instrucciones de la ruta de ejecución correcta. En pocas palabras: un gran éxito de rendimiento.
Por lo tanto, la razón por la que las declaraciones son caras se debe a errores de predicción de las sucursales . Esto es solo en el nivel más bajo. Si está escribiendo código de alto nivel, no necesita preocuparse por estos detalles en absoluto. Solo debería preocuparse por esto si está escribiendo código extremadamente crítico para el rendimiento en C o ensamblado. Si ese es el caso, escribir código sin ramificaciones a menudo puede ser superior al código que se ramifica, incluso si se necesitan varias instrucciones más. Hay algunos trucos de bits haciendo girar los que puede hacer para calcular cosas tales como abs()
, min()
y max()
sin ramificación.