Czy istnieje podpowiedź dla kompilatora GCC, aby wymusić przewidywanie gałęzi, aby zawsze szło w określony sposób?

Dla architektur Intela, czy istnieje sposób, aby polecić kompilatorowi GCC generowanie kodu, który zawsze wymusza przewidywanie gałęzi w moim kodzie? Czy sprzęt Intela w ogóle to obsługuje? A co z innymi kompilatorami lub hardwarami?

Użyłbym tego w kodzie C++, gdzie znam przypadek, że chcę działać szybko i nie dbam o spowolnienie, gdy inna gałąź musi być brana, nawet jeśli ostatnio wzięła tę gałąź.

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

Jako kontynuacja pytanie do Evdzhan Mustafa, czy podpowiedź może tylko podać podpowiedź, gdy procesor pierwszy raz napotka instrukcję, wszystkie kolejne odgałęzienia, działają normalnie?

Author: Bergi, 2015-05-08

7 answers

Poprawny sposób definiowania prawdopodobnych / nieprawdopodobnych makr w C++11 jest następujący:

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

Gdy te makra zdefiniowano w ten sposób:

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

, które mogą zmienić znaczenie if wypowiedzi i złamać kod. Rozważmy następujący kod:

#include <iostream>

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";
}

I jego wyjście:

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

Jak widzisz, definicja prawdopodobnego użycia !! jako rzut do bool łamie semantykę if.

Nie chodzi tu o to, że operator int() i operator bool() powinny być ze sobą powiązane. Co jest dobrą praktyką.

Raczej użycie !!(x) zamiast static_cast<bool>(x) traci kontekst dla C++11 konwersji kontekstowych .

 15
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-09 14:35:10

GCC obsługuje funkcję __builtin_expect(long exp, long c) w celu zapewnienia tego rodzaju funkcji. Możesz sprawdzić dokumentację tutaj .

Gdzie exp jest użytym warunkiem, a {[4] } jest wartością oczekiwaną. Na przykład w Twoim przypadku chciałbyś

if (__builtin_expect(normal, 1))

Ze względu na niezręczną składnię jest to zwykle używane przez zdefiniowanie dwóch niestandardowych makr, takich jak

#define likely(x)    __builtin_expect (!!(x), 1)
#define unlikely(x)  __builtin_expect (!!(x), 0)
Żeby ułatwić zadanie.

Pamiętaj, że:

  1. to jest niestandardowe
  2. a kompilator / cpu branch predictor są prawdopodobnie bardziej wykwalifikowani niż ty w podejmowaniu takich decyzji, więc może to być przedwczesna mikro-optymalizacja
 78
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-08 19:07:02

Gcc ma long _ _ builtin_expect( long exp, long c) ( / align = "left" / ):

Możesz użyć _ _ builtin _ expect, aby zapewnić kompilatorowi gałąź informacje o prognozach. Ogólnie rzecz biorąc, powinieneś preferować używanie rzeczywistych sprzężenie zwrotne profilu dla tego (- fprofile-arcs), ponieważ programiści są notorycznie zły w przewidywaniu, jak ich programy faktycznie działają . Istnieją jednak aplikacje, w których dane te są trudne do zebrania.

Powrót wartość to wartość exp, która powinna być całką ekspresja. Semantyka wbudowanego polega na tym, że oczekuje się, że exp = = c. na przykład:

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

Wskazuje, że nie spodziewamy się wywoływać foo, ponieważ oczekujemy, że x będzie zero. Ponieważ ograniczasz się do wyrażeń całkowych dla exp, należy stosować konstrukcje takie jak

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

Podczas testowania wartości wskaźnika lub zmiennoprzecinkowych.

Jak zauważa dokumentacja, powinieneś preferować użycie rzeczywistych informacje zwrotne o profilu i ten artykuł pokazuje praktyczny przykład tego i jak to w ich przypadku przynajmniej kończy się ulepszeniem w stosunku do używania __builtin_expect. Zobacz także Jak używać optymalizacji z przewodnikiem w g++?.

Możemy również znaleźć artykuł dla początkujących użytkowników jądra Linuksa na temat makr kernal prawdopodobnie () i mało prawdopodobne () , które używają tej funkcji:

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

Zwróć uwagę na !! użyte w makrze wyjaśnienie tego możemy znaleźć w dlaczego warto używać !!(warunek) zamiast (warunek)?.

To, że ta technika jest używana w jądrze Linuksa, nie oznacza, że zawsze ma sens jej używanie. Widzimy z tego pytania, na które ostatnio odpowiedziałem różnica między wydajnością funkcji przy przekazywaniu parametru jako stałej czasowej kompilacji lub zmiennej , że wiele ręcznie zwijanych technik optymalizacji nie działa w ogólnym przypadku. Musimy uważnie profilować kod, aby zrozumieć, czy dana technika jest skuteczna. Wiele starych technik może nawet nie być istotne z nowoczesnymi optymalizacjami kompilatorów.

Uwaga, chociaż builtiny nie są przenośne clang obsługuje również _ _ builtin_expect .

Również na niektórych architekturach może to nie robić różnicy .

 41
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:41

Nie, Nie ma. (Przynajmniej na nowoczesnych procesorach x86.)

__builtin_expect wspomniane w innych odpowiedziach wpływa na sposób, w jaki gcc organizuje kod assembly. to niebezpośrednio wpływa na predyktor gałęzi procesora. oczywiście, będzie to miało pośredni wpływ na przewidywanie gałęzi, spowodowany zmianą kolejności kodu. Ale na nowoczesnych procesorach x86 nie ma instrukcji, która mówi procesorowi "Załóżmy, że ta gałąź jest/nie jest zajęta".

Zobacz to pytanie, aby uzyskać więcej szczegółów: Intel x86 0x2e/0x3e prefix Branch prediction rzeczywiście używany?

Aby było jasne, __builtin_expect i / lub użycie -fprofile-arcs może poprawić wydajność kodu, zarówno poprzez podpowiedzi do predyktora gałęzi poprzez układ kodu (zobacz Optymalizacja wydajności x86-64 assembly-Alignment and branch prediction ), jak i poprawę zachowania pamięci podręcznej poprzez trzymanie "mało prawdopodobnego" kodu z dala od" prawdopodobnego " kodu.

 38
Author: Artelius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:59

Jak wszystkie inne odpowiedzi zostały odpowiednio zasugerowane, możesz użyć __builtin_expect, aby dać kompilatorowi podpowiedź, jak zorganizować KOD złożenia. Jak[7]}oficjalne dokumenty wskazują, w większości przypadków asembler wbudowany w twój mózg nie będzie tak dobry jak ten stworzony przez zespół GCC. Zawsze najlepiej jest użyć rzeczywistych danych profilu, aby zoptymalizować kod, a nie zgadywać.

W podobny sposób, ale jeszcze nie wymieniony, jest specyficznym dla GCC sposobem zmuszania kompilatora do generowania kodu na "zimnej" ścieżce. Wiąże się to z użyciem atrybutów noinline i cold, które robią dokładnie to, co brzmią. Te atrybuty mogą być stosowane tylko do funkcji, ale w C++11, Można zadeklarować funkcje lambda inline i te dwa atrybuty mogą być również stosowane do funkcji lambda.

Mimo, że nadal mieści się to w ogólnej kategorii mikro-optymalizacji, a więc obowiązuje standardowa porada-test nie zgaduj-wydaje mi się, że jest bardziej ogólnie przydatna niż __builtin_expect. Hardly wszystkie generacje procesora x86 używają podpowiedzi do przewidywania gałęzi (reference), więc jedyną rzeczą, na którą i tak będziesz mógł wpłynąć, jest kolejność kodu złożenia. Ponieważ wiesz, co to jest obsługa błędów lub kod "edge case", możesz użyć tej adnotacji, aby upewnić się, że kompilator nigdy nie przewidzi gałęzi do niego i połączy ją z "gorącym" kodem podczas optymalizacji rozmiaru.

Przykładowe użycie:

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.
        [&]() __attribute__((noinline,cold)) {
            HandleError(...);
        }();
    }

    // Do normal stuff
    ⋮
}

Jeszcze lepiej, GCC automatycznie zignoruje to w preferowanie informacji zwrotnych o Profilu, gdy jest dostępna (np. przy kompilacji z -fprofile-use).

Zobacz oficjalną dokumentację tutaj: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

 15
Author: Cody Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-09 10:07:54

_ _ builtin _ expect może być użyty do wskazania kompilatorowi, w którą stronę ma iść branch. Może to wpłynąć na sposób generowania kodu. Typowe procesory uruchamiają kod szybciej sekwencyjnie. Więc jeśli napiszesz

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

Kompilator wygeneruje kod w postaci

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;

Jeśli podpowiedź jest prawidłowa, spowoduje to wykonanie kodu bez żadnych gałęzi. Będzie działać szybciej niż normalna Sekwencja, w której każda instrukcja if rozgałęzi się wokół kodu warunkowego i wykona trzy gałęzie.

Nowsze procesory x86 mają instrukcje dla gałęzi, które mają być brane, lub dla gałęzi, które nie mają być brane (jest prefiks instrukcji; nie jestem pewien szczegółów). Nie wiem, czy procesor tego używa. Nie jest to zbyt przydatne, ponieważ przewidywanie gałęzi poradzi sobie z tym dobrze. Więc nie sądzę, że można rzeczywiście wpływać na gałąź przewidywania .

 3
Author: gnasher729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-08 23:22:20

W odniesieniu do OP, nie, w GCC nie ma sposobu, aby powiedzieć procesorowi, aby zawsze zakładał, że gałąź jest lub nie jest zajęta. To, co masz, to_ _ builtin _ expect, który robi to, co mówią inni. Co więcej, myślę, że nie chcesz mówić procesorowi, czy branch jest zajęty, czy nie Zawsze . Dzisiejsze procesory, takie jak architektura Intela, potrafią rozpoznawać dość złożone wzorce i skutecznie się dostosowywać.

Są jednak chwile, kiedy chcesz przejąć kontrolę nad tym, czy domyślnie branch jest przewidywany lub nie: gdy wiesz, że kod będzie nazywany "zimnym" w odniesieniu do statystyk rozgałęzień.

Jeden konkretny przykład: kod zarządzania wyjątkami. Z definicji kod zarządzania stanie się wyjątkowo, ale być może, gdy wystąpi maksymalna wydajność jest pożądana (może wystąpić błąd krytyczny, aby dbać o jak najszybciej), dlatego możesz chcieć kontrolować domyślne przewidywanie.

Inny przykład: możesz sklasyfikować swoje wprowadź i wskakuj do kodu, który obsługuje wynik Twojej klasyfikacji. Jeśli istnieje wiele klasyfikacji, procesor może zbierać statystyki, ale je stracić, ponieważ ta sama klasyfikacja nie dzieje się wystarczająco szybko, a zasoby przewidywania są poświęcone ostatnio nazwanemu kodowi. Szkoda, że nie byłoby prymitywne powiedzieć procesorowi "proszę nie poświęcać zasobów przewidywania do tego kodu" sposób, w jaki czasami można powiedzieć "nie buforować tego".

 0
Author: EdMaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-01 02:28:44