Existe-t-il un conseil au compilateur pour que GCC force la prédiction de twig à toujours aller dans un certain sens?

Pour les architectures Intel, existe-t-il un moyen de demander au compilateur GCC de générer du code qui force toujours la prédiction de twig d’une manière particulière dans mon code? Est-ce que le matériel Intel supporte même cela? Qu’en est-il des autres compilateurs ou matériels?

Je voudrais utiliser cela dans le code C ++ où je connais le cas que je souhaite exécuter rapidement et ne me soucie pas du ralentissement lorsque l’autre twig doit être prise même quand il a pris récemment cette twig.

for (;;) { if (normal) { // How to tell comstackr to always branch predict true value? doSomethingNormal(); } else { exceptionalCase(); } } 

En guise de suivi pour Evdzhan Mustafa, le conseil peut-il simplement spécifier un indice pour la première fois que le processeur rencontre l’instruction, toutes les prédictions de twig suivantes, fonctionnant normalement?

    La manière correcte de définir des macros probables / improbables dans C ++ 11 est la suivante:

     #define LIKELY(condition) __builtin_expect(static_cast(condition), 1) #define UNLIKELY(condition) __builtin_expect(static_cast(condition), 0) 

    Lorsque ces macros ont défini cette manière:

     #define LIKELY(condition) __builtin_expect(!!(condition), 1) 

    Cela peut changer le sens des instructions if et casser le code. Considérez le code suivant:

     #include  struct A { explicit operator bool() const { return true; } operator int() const { return 0; } }; #define LIKELY(condition) __builtin_expect((condition), 1) int main() { A a; if(a) std::cout << "if(a) is true\n"; if(LIKELY(a)) std::cout << "if(LIKELY(a)) is true\n"; else std::cout << "if(LIKELY(a)) is false\n"; } 

    Et sa sortie:

     if(a) is true if(LIKELY(a)) is false 

    Comme vous pouvez le voir, la définition de l'utilisation PROBABLE !! comme un cast à bool brise la sémantique de if .

    Le point ici n'est pas que l' operator int() et l' operator bool() doivent être liés. Ce qui est une bonne pratique.

    Plutôt que d'utiliser !!(x) au lieu de static_cast(x) perd le contexte pour les conversions contextuelles C ++ 11 .

    GCC supporte la fonction __builtin_expect(long exp, long c) pour fournir ce type de fonctionnalité. Vous pouvez consulter la documentation ici .

    exp est la condition utilisée et c est la valeur attendue. Par exemple, dans votre cas, vous voudriez

     if (__builtin_expect(normal, 1)) 

    En raison de la syntaxe maladroite, elle est généralement utilisée en définissant deux macros personnalisées comme

     #define likely(x) __builtin_expect (!!(x), 1) #define unlikely(x) __builtin_expect (!!(x), 0) 

    juste pour faciliter la tâche.

    Faites attention à cela:

    1. c’est non standard
    2. un prédicteur de twig compilateur / processeur est probablement plus compétent que vous pour décider de telles choses, donc cela pourrait être une micro-optimisation prématurée

    gcc a long __builtin_expect (long exp, long c) ( accent mis sur le mien ):

    Vous pouvez utiliser __builtin_expect pour fournir au compilateur des informations de prédiction de twig. En général, vous devriez préférer utiliser la rétroaction de profil pour cette fonction (-fprofile-arcs), car les programmeurs sont notoirement mauvais pour prédire la performance réelle de leurs programmes . Cependant, il existe des applications dans lesquelles ces données sont difficiles à collecter.

    La valeur de retour est la valeur de exp, qui devrait être une expression intégrale. La sémantique de la fonction intégrée est qu’il est attendu que exp == c. Par exemple:

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

    indique que nous ne nous attendons pas à appeler foo, puisque nous nous attendons à ce que x soit nul. Puisque vous êtes limité aux expressions intégrales pour exp, vous devez utiliser des constructions telles que

     if (__builtin_expect (ptr != NULL, 1)) foo (*ptr); 

    lors du test de valeurs de pointeur ou de virgule flottante.

    Comme le note la documentation, vous devriez préférer utiliser les commentaires de profils réels et cet article montre un exemple pratique de ceci et comment, dans leur cas, cela finit par être une amélioration par rapport à l’utilisation de __builtin_expect . Voir également Comment utiliser les optimisations guidées par profils dans g ++? .

    Nous pouvons également trouver un article sur les nouveaux kernel Linux sur les macros kernal probables () et les improbables () qui utilisent cette fonctionnalité:

     #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 

    Notez le !! utilisé dans la macro, nous pouvons trouver l’explication de cela dans Pourquoi utiliser !! (condition) au lieu de (condition)? .

    Ce n’est pas parce que cette technique est utilisée dans le kernel Linux que l’utilisation de cette technique est toujours appropriée. Nous pouvons voir à partir de cette question que j’ai récemment répondu à la différence entre la performance de la fonction lors du passage du paramètre en tant que constante de compilation ou variable que de nombreuses techniques d’optimisations roulées manuellement ne fonctionnent pas dans le cas général. Nous devons définir le code avec soin pour comprendre si une technique est efficace. De nombreuses techniques anciennes peuvent même ne pas être pertinentes avec les optimisations modernes du compilateur.

    Remarque, bien que les commandes intégrées ne soient pas portables, clang supporte également __builtin_expect .

    Même sur certaines architectures, cela peut ne pas faire de différence .

    Non, il n’y en a pas. (Au moins sur les processeurs x86 modernes.)

    __builtin_expect mentionné dans d’autres réponses influence la façon dont gcc organise le code de l’assemblage. Cela n’influence pas directement le prédicteur de twig du CPU. Bien sûr, il y aura des effets indirects sur la prédiction de twig provoquée par la réorganisation du code. Mais sur les processeurs x86 modernes, aucune instruction ne dit au processeur “suppose que cette twig est / n’est pas prise”.

    Voir cette question pour plus de détails: Prédiction de twig Intel x86 0x2E / 0x3E Prefix réellement utilisée?

    Pour être clair, __builtin_expect et / ou l’utilisation de -fprofile-arcs peuvent améliorer les performances de votre code, en donnant des indications au prédicteur de twig via la disposition du code (voir Optimisations des performances de l’assembly x86-64 – Prévision d’alignement et de twig ), et aussi améliorer le comportement du cache en gardant le code “peu probable” loin du code “probable”.

    Comme les autres réponses ont toutes été suggérées de manière adéquate, vous pouvez utiliser __builtin_expect pour donner au compilateur un indice sur la façon d’organiser le code de l’assembly. Comme le soulignent les documents officiels , dans la plupart des cas, l’assembleur intégré à votre cerveau ne sera pas aussi bon que celui conçu par l’équipe de GCC. Il est toujours préférable d’utiliser les données de profil réelles pour optimiser votre code, plutôt que de deviner.

    Dans le même ordre d’idées, mais pas encore mentionné, il existe un moyen spécifique au GCC de forcer le compilateur à générer du code sur un chemin “froid”. Cela implique l’utilisation des atsortingbuts noinline et cold , qui font exactement ce qu’ils font. Ces atsortingbuts ne peuvent être appliqués qu’à des fonctions, mais avec C ++ 11, vous pouvez déclarer des fonctions lambda en ligne et ces deux atsortingbuts peuvent également être appliqués à des fonctions lambda.

    Bien que cela fasse toujours partie de la catégorie générale de la micro-optimisation et que, par conséquent, les conseils standard s’appliquent – le test ne devine pas – j’ai l’impression qu’il est plus généralement utile que __builtin_expect . Presque toutes les générations du processeur x86 utilisent des indicateurs de prédiction de twig ( référence ), de sorte que la seule chose que vous pouvez affecter de toute façon est l’ordre du code d’assemblage. Puisque vous savez ce qu’est le traitement des erreurs ou le code “case d’extrémité”, vous pouvez utiliser cette annotation pour vous assurer que le compilateur ne prévoira jamais une twig et le liera au code “chaud” lors de l’optimisation de la taille.

    Exemple d’utilisation:

     void FooTheBar(void* pFoo) { if (pFoo == nullptr) { // Oh no! A null pointer is an error, but maybe this is a public-facing // function, so we have to be prepared for anything. Yet, we don't want // the error-handling code to fill up the instruction cache, so we will // force it out-of-line and onto a "cold" path. [&]() __atsortingbute__((noinline,cold)) { HandleError(...); }(); } // Do normal stuff ⋮ } 

    Mieux encore, GCC l’ignorera automatiquement en faveur de la rétroaction du profil lorsqu’il sera disponible (par exemple, lors de la compilation avec -fprofile-use ).

    Voir la documentation officielle ici: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Atsortingbutes.html#Common-Function-Atsortingbutes

    __builtin_expect peut être utilisé pour indiquer au compilateur dans quelle direction une twig doit être utilisée. Cela peut influencer la manière dont le code est généré. Les processeurs classiques exécutent le code plus rapidement de manière séquentielle. Donc, si vous écrivez

     if (__builtin_expect (x == 0, 0)) ++count; if (__builtin_expect (y == 0, 0)) ++count; if (__builtin_expect (z == 0, 0)) ++count; 

    le compilateur va générer du code comme

     if (x == 0) goto if1; back1: if (y == 0) goto if2; back2: if (z == 0) goto if3; back3: ; ... if1: ++count; goto back1; if2: ++count; goto back2; if3: ++count; goto back3; 

    Si votre indice est correct, le code sera exécuté sans aucune twig réellement exécutée. Il fonctionnera plus vite que la séquence normale, où chaque instruction if contournerait le code conditionnel et exécuterait trois twigs.

    Les nouveaux processeurs x86 ont des instructions pour les twigs qui doivent être sockets, ou pour les twigs qui ne doivent pas être sockets (il y a un préfixe d’instruction, pas sûr des détails). Pas sûr si le processeur utilise cela. Ce n’est pas très utile, car la prédiction de twig va gérer cela très bien. Donc, je ne pense pas que vous pouvez réellement influencer la prédiction de twig.

    En ce qui concerne l’OP, non, GCC n’a aucun moyen de dire au processeur de toujours supposer que la twig est ou n’est pas prise. Ce que vous avez, c’est __builtin_expect, qui fait ce que les autres disent. De plus, je pense que vous ne voulez pas dire au processeur si la twig est prise ou pas toujours . Les processeurs actuels, tels que l’architecture Intel, peuvent reconnaître des modèles assez complexes et s’adapter efficacement.

    Cependant, il arrive que vous vouliez contrôler si, par défaut, une twig est prédite ou non: Lorsque vous connaissez le code, il sera appelé “froid” en ce qui concerne les statistiques de twigment.

    Un exemple concret: code de gestion des exceptions. Par définition, le code de gestion se produira exceptionnellement, mais peut-être que lorsque cela se produit, les performances maximales sont souhaitées (il peut y avoir une erreur critique à prendre soin le plus tôt possible), vous pouvez donc contrôler la prédiction par défaut.

    Autre exemple: vous pouvez classer votre saisie et accéder au code qui gère le résultat de votre classification. S’il y a beaucoup de classifications, le processeur peut collecter des statistiques mais les perdre car la même classification n’arrive pas assez tôt et les ressources de prédiction sont consacrées au code récemment appelé. Je souhaite qu’il y ait une primitive pour dire au processeur “s’il vous plaît ne pas consacrer de ressources de prédiction à ce code” comme vous pouvez parfois dire “ne pas mettre en cache ceci”.