web-dev-qa-db-fra.com

Est-il possible d'indiquer au prédicteur de branche la probabilité de suivre la branche?

Juste pour être clair, je ne vais pas pour toute sorte de portabilité ici, donc toutes les solutions qui me lieront à une certaine boîte sont très bien.

Fondamentalement, j'ai une instruction if qui sera évaluée à 99% du temps comme vraie, et j'essaie d'extraire chaque dernière horloge de performance, puis-je émettre une sorte de commande de compilateur (en utilisant GCC 4.1.2 et x86 ISA, si il importe) de dire au prédicteur de branche qu'il doit mettre en cache pour cette branche?

73
Andy Shulman

Oui. http://kerneltrap.org/node/4705

Le __builtin_expect est une méthode que gcc (versions> = 2.96) propose aux programmeurs d'indiquer au compilateur des informations de prédiction de branche. La valeur de retour de __builtin_expect est le premier argument (qui ne peut être qu'un entier) qui lui est passé.

if (__builtin_expect (x, 0))
                foo ();

     [This] would indicate that we do not expect to call `foo', since we
     expect `x' to be zero. 
57
Drakosha

Oui, mais cela n'aura aucun effet . Les exceptions sont des architectures plus anciennes (obsolètes) avant Netburst, et même alors, cela ne fait rien de mesurable.

Il y a un opcode "branch hint" Intel introduit avec l'architecture Netburst, et une prédiction de branche statique par défaut pour les sauts à froid (prises prédites en arrière, prises non prédites en avant) sur certaines architectures plus anciennes. GCC implémente cela avec la __builtin_expect (x, prediction), où la prédiction est généralement 0 ou 1. L'opcode émis par le compilateur est ignoré sur tous les nouveaux architectures de processeur (> = Core 2). Le petit cas d'angle où cela fait réellement quelque chose est le cas d'un saut à froid sur l'ancienne architecture Netburst. Intel recommande maintenant de ne pas utiliser les conseils de branche statiques, probablement parce qu'ils considèrent l'augmentation de la taille du code plus nuisible que l'accélération marginale possible.

Outre l'indicateur de branche inutile pour le prédicteur, __builtin_expect a son utilité, le compilateur peut réorganiser le code pour améliorer l'utilisation du cache ou économiser de la mémoire.

Il y a plusieurs raisons pour lesquelles cela ne fonctionne pas comme prévu.

  • Le processeur peut parfaitement prédire les petites boucles (n <64).
  • Le processeur peut parfaitement prédire les petits motifs répétitifs (n ~ 7).
  • Le processeur lui-même peut mieux estimer la probabilité d'une branche pendant l'exécution que le compilateur/programmeur pendant la compilation.
  • La prévisibilité (= probabilité qu'une branche sera correctement prédite) d'une branche est beaucoup plus importante que la probabilité que la branche soit prise. Malheureusement, cela dépend fortement de l'architecture et il est notoirement difficile de prédire la prévisibilité de la branche.

En savoir plus sur les travaux internes de la prédiction de branche sur Agner Fogs manuels . Voir aussi le gcc liste de diffusion .

71
Gunther Piez

Pentium 4 (alias microarchitecture Netburst) avait des indices de prédicteur de branche comme préfixes des instructions jcc, mais seul P4 n'a jamais rien fait avec eux. Voir http://ref.x86asm.net/geek32.html . Et Section 3.5 de l'excellent guide opt asm d'Agner Fog , de http://www.agner.org/optimize/ . Il a également un guide pour l'optimisation en C++.

Les processeurs x86 antérieurs et ultérieurs ignorent silencieusement ces octets de préfixe. Y a-t-il des résultats de test de performance pour l'utilisation d'indices probables/improbables? mentionne que PowerPC a des instructions de saut qui ont un indice de prédiction de branche dans le cadre de l'encodage. C'est une caractéristique architecturale assez rare. La prédiction statique des branches au moment de la compilation est très difficile à faire avec précision, il est donc généralement préférable de laisser le soin au matériel de le comprendre.

Peu de choses sont officiellement publiées sur le comportement exact des prédicteurs de branche et des tampons de cible de branche dans les processeurs Intel et AMD les plus récents. Les manuels d'optimisation (faciles à trouver sur les sites Web d'AMD et d'Intel) donnent quelques conseils, mais ne documentent pas un comportement spécifique. Certaines personnes ont effectué des tests pour essayer de deviner l'implémentation, par exemple combien d'entrées BTB Core2 a ... Quoi qu'il en soit, l'idée d'indiquer explicitement le prédicteur a été abandonnée (pour l'instant).

Ce qui est documenté est par exemple que Core2 a un tampon d'historique de branche qui peut éviter de mal interpréter la sortie de boucle si la boucle exécute toujours un nombre court et constant d'itérations, <8 ou 16 IIRC. Mais ne soyez pas trop rapide à dérouler, car une boucle qui tient dans 64 octets (ou 19uops sur Penryn) n'aura pas de goulots d'étranglement pour la récupération des instructions car elle est relue à partir d'un tampon ... allez lire les pdfs d'Agner Fog, c'est excellent.

Voir aussi Pourquoi Intel a-t-il modifié le mécanisme de prédiction de branche statique au cours de ces années? : Intel puisque Sandybridge n'utilise pas du tout la prédiction statique, pour autant que nous puissions en juger par les expériences de performances qui tentent de procéder à une rétro-ingénierie ce que font les CPU. (De nombreux processeurs plus anciens ont une prédiction statique comme solution de rechange lorsque la prédiction dynamique échoue. La prédiction statique normale est que les branches avant ne sont pas prises et les branches arrière sont prises (car les branches arrière sont souvent des branches en boucle).)


L'effet des macros likely()/unlikely() en utilisant GNU C's __builtin_expect (comme le mentionne la réponse de Drakosha) pas insère directement des indices BP dans l'asm . (Cela pourrait éventuellement être le cas avec gcc -march=pentium4, mais pas lors de la compilation pour autre chose).

L'effet réel est de disposer le code afin que le chemin rapide ait moins de branches prises et peut-être moins d'instructions au total. Cela aidera la prédiction de branche dans les cas où la prédiction statique entre en jeu (par exemple, les prédicteurs dynamiques sont froids, sur les processeurs qui retombent à la prédiction statique au lieu de simplement laisser les branches s'alias les unes les autres dans les caches de prédicteur.)

Voir Quel est l'avantage de __builtin_expect de GCC dans les instructions if else? pour un exemple spécifique de code-gen.

Les branches prises coûtent un peu plus cher que les branches non prises, même si elles sont parfaitement prédites. Lorsque le CPU récupère le code par blocs de 16 octets à décoder en parallèle, une branche prise signifie que les instructions ultérieures de ce bloc d'extraction ne font pas partie du flux d'instructions à exécuter. Il crée des bulles dans le front-end qui peuvent devenir un goulot d'étranglement dans le code à haut débit (qui ne bloque pas dans le back-end en cas de manque de cache et a un parallélisme de niveau instruction élevé).

Le fait de sauter entre différents blocs peut également toucher plus de lignes de cache de code , augmentant l'empreinte du cache L1i et provoquant peut-être plus de ratés du cache d'instructions s'il faisait froid. (Et potentiellement l'empreinte uop-cache). C'est donc un autre avantage d'avoir la voie rapide courte et linéaire.


L'optimisation guidée par le profil de GCC rend normalement inutiles les macros probables/improbables. Le compilateur collecte des données d'exécution sur la façon dont chaque branche a pris des décisions de disposition de code et pour identifier les blocs/fonctions chauds et froids. (Par exemple, il déroulera les boucles dans les fonctions chaudes mais pas dans les fonctions froides.) Voir -fprofile-generate et -fprofile-usedans le manuel GCC . Comment utiliser les optimisations guidées par les profils dans g ++?

Sinon, GCC doit deviner en utilisant diverses heuristiques, si vous n'avez pas utilisé de macros probables/improbables et n'avez pas utilisé PGO. -fguess-branch-probability est activé par défaut à -O1 et plus haut.

https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1 a des résultats de référence pour PGO par rapport à normal avec gcc8.2 sur un processeur Xeon Scalable Server . (Skylake-AVX512). Chaque indice de référence a obtenu au moins une petite accélération, et certains ont profité de ~ 10%. (La plupart de cela provient probablement du déroulement des boucles dans les boucles chaudes, mais une partie provient probablement d'une meilleure disposition des branches et d'autres effets.)

31
Peter Cordes

Je suggère plutôt que de m'inquiéter de la prédiction des branches, de profiler le code et d'optimiser le code pour réduire le nombre de branches. Un exemple est le déroulement de boucle et un autre utilisant des techniques de programmation booléenne plutôt que d'utiliser des instructions if.

La plupart des processeurs adorent extraire les instructions. En règle générale, une instruction de branche génère un fault dans le processeur, ce qui entraîne le vidage de la file d'attente de prélecture. C'est là que se trouve la plus grosse pénalité. Pour réduire ce temps de pénalité, réécrivez (et concevez) le code afin que moins de branches soient disponibles. De plus, certains processeurs peuvent exécuter des instructions de manière conditionnelle sans avoir à créer de branche.

J'ai optimisé un programme d'une heure d'exécution à 2 minutes en utilisant le déroulement de boucle et de grands tampons d'E/S. La prévision de branche n'aurait pas permis de gagner beaucoup de temps dans ce cas.

6
Thomas Matthews

Sun C Studio a défini certains pragmas pour ce cas.

#pragma rarement_appelé ()

Cela fonctionne si une partie d'une expression conditionnelle est un appel de fonction ou commence par un appel de fonction.

Mais il n'y a aucun moyen de baliser une instruction if/while générique

1
Lothar