Quel code est le meilleur pour l'optimisation des prédictions de branche

10

Compte tenu de la prédiction de branche et de l'effet des optimisations du compilateur, quel code a tendance à offrir des performances supérieures?

Notez que bRareExceptionPresent représente une condition inhabituelle. Ce n'est pas le chemin normal de la logique.

/* MOST COMMON path must branch around IF clause */

bool SomeFunction(bool bRareExceptionPresent)
{
  // abort before function
  if(bRareExceptionPresent)
  {
     return false;
  }    
  .. function primary body ..    
  return true;
}

/* MOST COMMON path does NOT branch */

bool SomeFunction(bool bRareExceptionPresent)
{
  if(!bRareExceptionPresent)
  {
    .. function primary body ..
  }
  else
  {
    return false;
  }
  return true;
}
dyasta
la source
9
Je vais sortir sur un membre ici et dire qu'il n'y a aucune différence que ce soit.
Robert Harvey
7
Cela dépend probablement du CPU spécifique pour lequel vous compilez, car ils ont différentes architectures de pipeline (slots à retard vs aucun slot à retard). Le temps que vous avez passé à y penser est probablement bien plus que le temps gagné lors de l'exécution - profilez d'abord, puis optimisez.
2
C'est presque certainement une micro-optimisation prématurée.
Robert Harvey
2
@MichaelT Oui, le profilage est en effet le seul moyen fiable de savoir ce qui se passe réellement avec les performances du code sur la plate-forme cible, dans son contexte. Cependant, j'étais curieux de savoir si l'un était généralement préféré.
dyasta
1
@RobertHarvey: Il s'agit d'une micro-optimisation prématurée, sauf dans les cas où les deux conditions sont remplies: (1) la boucle est appelée des milliards (et non des millions) de fois; et (2) ironiquement, lorsque le corps de la boucle est minuscule en termes de code machine. La condition n ° 2 signifie que la fraction du temps consacré aux frais généraux n'est pas négligeable par rapport au temps consacré à un travail utile. La bonne nouvelle est qu'en général, dans de telles situations où les deux conditions sont remplies, SIMD (vectorisation), qui est par nature sans branche, résoudra tous les problèmes de performances.
rwong

Réponses:

10

Dans le monde d'aujourd'hui, peu importe, voire pas du tout.

La prédiction de branche dynamique (quelque chose à laquelle on a pensé pendant des décennies (voir une analyse des charges de travail du système Schemeson de prédiction de branche dynamique publiée en 1996)) est assez courante.

Un exemple de cela peut être trouvé dans le processeur ARM. Du Arm Info Centre on Branch Prediction

Pour améliorer la précision de la prédiction de branchement, une combinaison de techniques statiques et dynamiques est employée.

La question est alors "qu'est-ce que la prédiction de branche dynamique dans le processeur de bras?" La lecture continue de la prédiction de branche dynamique montre qu'elle utilise un schéma de prédiction à 2 bits (décrit dans l'article) construit des informations sur si la branche est fortement ou faiblement prise ou non prise.

Au fil du temps (et par temps je veux dire quelques passages à travers ce bloc) cela accumule des informations sur la direction que prendra le code.

Pour la prédiction statique , il examine la façon dont le code se présente et la façon dont la branche est faite sur le test - à une instruction précédente ou à une autre dans le code:

Le schéma utilisé dans le processeur ARM1136JF-S prédit que toutes les branches conditionnelles avant ne sont pas prises et toutes les branches arrière sont prises. Environ 65% de toutes les branches sont précédées d'un nombre suffisant de cycles hors branche pour être complètement prédites.

Comme mentionné par Sparky, cela est basé sur la compréhension que les boucles, le plus souvent, bouclent. La boucle se ramifie vers l'arrière (elle a une branche à la fin de la boucle pour la redémarrer en haut) - elle le fait normalement.

Le danger d'essayer de deviner le compilateur est que vous ne savez pas comment ce code va être compilé (et optimisé). Et pour la plupart, cela n'a pas d'importance. Avec la prédiction dynamique, deux fois par le biais de la fonction, il prédira un saut sur l'instruction de garde pour un retour prématuré. Si la performance de deux pipelines vidés est d'une performance critique, il y a d'autres choses à craindre.

Le temps qu'il faut pour lire un style sur l'autre est probablement plus important - rendre le code propre pour qu'un humain puisse le lire, car le compilateur va très bien, peu importe à quel point vous êtes désordonné ou idéalisé, vous écrivez le code.


la source
7
Une célèbre question de stackoverflow a montré que la prédiction de branche est importante, même aujourd'hui.
Florian Margaine
3
@FlorianMargaine, bien que cela ait de l'importance, se retrouver dans une situation où cela a vraiment de l'importance semble nécessiter la compréhension de ce que vous compilez et de son fonctionnement (bras vs x86 vs mips ...). L'écriture de code essayant de faire cette micro-optimisation au début fonctionne probablement à partir de prémisses erronées et n'atteint pas l'effet souhaité.
Bien sûr, ne citons pas DK. Mais je pense que cette question était clairement dans le sens de l'optimisation, alors que vous avez déjà dépassé la phase de profilage. :-)
Florian Margaine
2
@MichaelT Belle réponse, et je suis tout à fait d'accord avec votre conclusion. Ce type de pré-profilage / optimisation abstraite peut certainement être contre-productif. Cela finit par être un jeu de devinettes, ce qui oblige à prendre des décisions de conception pour des raisons irrationnelles. Pourtant, je me suis trouvé curieux; o
dyasta
5
@ 90h stackoverflow.com/questions/11227809/…
Florian Margaine
9

Ma compréhension est que la première fois que le CPU rencontre une branche, il prédira (si pris en charge) que les branches avant ne sont pas prises et les branches arrière le sont. La raison en est que les boucles (qui se ramifient généralement en arrière) sont supposées être prises.

Sur certains processeurs, vous pouvez indiquer dans l'instruction d'assemblage le chemin le plus probable. Les détails de cela m'échappent pour le moment.

En outre, certains compilateurs C prennent également en charge la prédiction de branche statique afin que vous puissiez indiquer au compilateur quelle branche est la plus probable. À son tour, il peut réorganiser le code généré, ou utiliser des instructions modifiées pour tirer parti de ces informations (ou même simplement les ignorer).

__builtin_expect((long)!!(x), 1L)  /* GNU C to indicate that <x> will likely be TRUE */
__builtin_expect((long)!!(x), 0L)  /* GNU C to indicate that <x> will likely be FALSE */

J'espère que cela t'aides.

Sparky
la source
3
"Ma compréhension est que la première fois que le CPU rencontre une branche, il prédira (s'il est pris en charge) que les branches avant ne sont pas prises et les branches arrière le sont." C'est une pensée très intéressante. Avez-vous des preuves que cela est effectivement mis en œuvre dans des architectures courantes?
blubb
5
Directement de la bouche du cheval: une branche avant par défaut n'est pas prise. Par défaut, une branche arrière est prise . Et à partir de la même page: "préfixe 0x3E - prédire statiquement une branche comme prise".
MSalters
Existe-t-il un pragma agnostique de plate-forme qui soit équivalent __builtin_expect?
MarcusJ