Au niveau le plus bas (dans le matériel), oui, si les s sont chers. Pour comprendre pourquoi, vous devez comprendre le fonctionnement des pipelines .
L'instruction courante à exécuter est stockée dans quelque chose généralement appelé le pointeur d'instruction (IP) ou le compteur de programme (PC); ces termes sont synonymes, mais différents termes sont utilisés avec différentes architectures. Pour la plupart des instructions, le PC de l'instruction suivante est simplement le PC actuel plus la longueur de l'instruction actuelle. Pour la plupart des architectures RISC, les instructions ont toutes une longueur constante, de sorte que le PC peut être incrémenté d'une quantité constante. Pour les architectures CISC telles que x86, les instructions peuvent être de longueur variable, de sorte que la logique qui décode l'instruction doit déterminer la durée de l'instruction en cours pour trouver l'emplacement de l'instruction suivante.
Pour les instructions de branchement , cependant, la prochaine instruction à exécuter n'est pas l'emplacement suivant après l'instruction en cours. Les branches sont des gotos - elles indiquent au processeur où se trouve la prochaine instruction. Les branches peuvent être conditionnelles ou inconditionnelles, et l'emplacement cible peut être fixe ou calculé.
Conditionnel ou inconditionnel est facile à comprendre - une branche conditionnelle n'est prise que si une certaine condition est satisfaite (par exemple si un nombre en équivaut à un autre); si la branche n'est pas prise, le contrôle passe à l'instruction suivante après la branche comme d'habitude. Pour les branches inconditionnelles, la branche est toujours prise. Les branches conditionnelles apparaissent dans les if
instructions et les tests de contrôle des boucles for
et while
. Les branches inconditionnelles apparaissent dans des boucles infinies, des appels de fonction, des retours de fonction break
et des continue
instructions, la tristement célèbre goto
instruction, et bien d'autres (ces listes sont loin d'être exhaustives).
La cible de la branche est un autre problème important. La plupart des branches ont une cible de branche fixe - elles vont à un emplacement spécifique dans le code qui est fixé au moment de la compilation. Cela inclut les if
instructions, les boucles de toutes sortes, les appels de fonction réguliers et bien d'autres. Les branches calculées calculent la cible de la branche lors de l'exécution. Cela inclut les switch
instructions (parfois), le retour d'une fonction, les appels de fonction virtuelle et les appels de pointeur de fonction.
Alors qu'est-ce que tout cela signifie pour la performance? Lorsque le processeur voit apparaître une instruction de branchement dans son pipeline, il doit déterminer comment continuer à remplir son pipeline. Afin de comprendre quelles instructions viennent après la branche dans le flux de programme, il a besoin de savoir deux choses: (1) si la branche sera prise et (2) la cible de la branche. Comprendre cela s'appelle la prédiction de branche , et c'est un problème difficile. Si le processeur devine correctement, le programme continue à pleine vitesse. Si au contraire le processeur ne devine pas correctement , il a juste passé un certain temps à calculer la mauvaise chose. Il doit maintenant vider son pipeline et le recharger avec des instructions provenant du chemin d'exécution correct. Bottom line: un grand succès en termes de performances.
Ainsi, la raison pour laquelle si les déclarations sont coûteuses est due à des erreurs de prédiction de la branche . Ce n'est qu'au niveau le plus bas. Si vous écrivez du code de haut niveau, vous n'avez pas du tout à vous soucier de ces détails. Vous ne devriez vous en soucier que si vous écrivez du code extrêmement critique en termes de performances en C ou en assembly. Si tel est le cas, l'écriture de code sans succursale peut souvent être supérieure au code qui se branche, même si plusieurs instructions supplémentaires sont nécessaires. Il y a quelques trucs de bit-tripotant cool que vous pouvez faire pour calculer des choses telles que abs()
, min()
et max()
sans ramification.