Czy w przypadku architektur Intela istnieje sposób, aby poinstruować kompilator GCC, aby generował kod, który zawsze wymusza przewidywanie gałęzi w określony sposób w moim kodzie? Czy sprzęt Intel w ogóle to obsługuje? A co z innymi kompilatorami lub oprogramowaniem sprzętowym?
Użyłbym tego w kodzie C ++, w którym znam przypadek, w którym chcę działać szybko i nie przejmuję się spowolnieniem, gdy inna gałąź musi zostać podjęta, nawet jeśli niedawno zajęła tę gałąź.
for (;;) {
if (normal) { // How to tell compiler to always branch predict true value?
doSomethingNormal();
} else {
exceptionalCase();
}
}
Jako następne pytanie dla Evdzhana Mustafy, czy wskazówka może po prostu podać wskazówkę po raz pierwszy, gdy procesor napotka instrukcję, wszystkie kolejne przewidywania gałęzi, działają normalnie?
Odpowiedzi:
Począwszy od C ++ 20 prawdopodobne i mało prawdopodobne atrybuty powinny być znormalizowane i są już obsługiwane w g ++ 9 . Jak omówiono tutaj , możesz pisać
np. w poniższym kodzie blok else jest wstawiany dzięki
[[unlikely]]
blokowi in the ifłącze godbolt porównujące obecność / brak atrybutu
źródło
[[unlikely]]
wif
vs[[likely]]
in theelse
?GCC obsługuje funkcję
__builtin_expect(long exp, long c)
dostarczania tego rodzaju funkcji. Możesz sprawdzić dokumentację tutaj .Gdzie
exp
jest używany warunek ic
jest wartością oczekiwaną. Na przykład, jeśli chceszZe względu na niezręczną składnię jest to zwykle używane przy definiowaniu dwóch niestandardowych makr, takich jak
tylko po to, aby ułatwić zadanie.
Weź to pod uwagę:
źródło
constexpr
funkcję?constexpr
funkcja mogła zastąpić to makro.if
Uważam, że musi to być bezpośrednio zawarte w oświadczeniu. Ten sam powódassert
nigdy nie mógł byćconstexpr
funkcją.constexpr
mówi tylko o semantyce wartości, a nie o wstawianiu zestawu specyficznego dla implementacji); prosta interpretacja (bez inline) kodu jest bez znaczenia. Nie ma żadnego powodu, aby używać do tego funkcji.__builtin_expect
samo w sobie jest wskazówką optymalizacyjną, więc twierdzenie, że metoda upraszczająca jej użycie zależy od optymalizacji, jest ... nie jest przekonujące. Ponadto nie dodałemconstexpr
specyfikatora, aby działał w pierwszej kolejności, ale aby działał w stałych wyrażeniach. I tak, istnieją powody, aby używać funkcji. Na przykład nie chciałbym zanieczyszczać całej mojej przestrzeni nazw uroczą małą nazwą, taką jaklikely
. Musiałbym użyć np.LIKELY
Żeby podkreślić, że jest to makro i uniknąć kolizji, ale to po prostu brzydkie.gcc ma long __builtin_expect (długie exp, długie c) ( moje podkreślenie ):
Jak zauważa dokumentacja, powinieneś preferować użycie rzeczywistych opinii o profilu, a ten artykuł pokazuje praktyczny przykład tego i jak w ich przypadku przynajmniej kończy się to ulepszeniem w stosunku do używania
__builtin_expect
. Zobacz także Jak korzystać z optymalizacji sterowanej profilem w g ++?.Możemy również znaleźć artykuł dla początkujących użytkowników jądra Linuksa na temat prawdopodobnie () i mało prawdopodobnych () makr jądra, które używają tej funkcji:
Zwróć uwagę na
!!
używane w makrze wyjaśnienie tego problemu w Dlaczego używać !! (warunek) zamiast (warunek)?.Tylko dlatego, że ta technika jest używana w jądrze Linuksa, nie oznacza, że zawsze ma sens. Z tego pytania widzimy, że ostatnio odpowiedziałem na różnicę między wydajnością funkcji podczas przekazywania parametru jako stałej czasu kompilacji lub zmiennej że wiele ręcznych technik optymalizacji nie działa w ogólnym przypadku. Musimy uważnie profilować kod, aby zrozumieć, czy technika jest skuteczna. Wiele starych technik może nawet nie mieć zastosowania w przypadku nowoczesnych optymalizacji kompilatorów.
Uwaga, chociaż wbudowane elementy nie są przenośne, clang obsługuje również __builtin_expect .
Również w przypadku niektórych architektur może to nie mieć znaczenia .
źródło
Nie, nie ma. (Przynajmniej na nowoczesnych procesorach x86).
__builtin_expect
wspomniany w innych odpowiedziach wpływa na sposób, w jaki gcc organizuje kod asemblera. Nie wpływa bezpośrednio na predyktor gałęzi procesora.Oczywiście, zmiana kolejności kodu będzie miała pośredni wpływ na przewidywanie gałęzi. 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: Czy faktycznie używana predykcja rozgałęzienia prefiksu Intel x86 0x2E / 0x3E?
Aby było jasne,
__builtin_expect
i / lub użycie-fprofile-arcs
może poprawić wydajność twojego kodu, zarówno poprzez udzielanie wskazówek predyktorowi gałęzi poprzez układ kodu (patrz Optymalizacja wydajności zestawu x86-64 - Wyrównanie i przewidywanie gałęzi ), a także poprawienie zachowania pamięci podręcznej zachowując „mało prawdopodobny” kod z dala od „prawdopodobnego” kodu.źródło
__builtin_expect
.__builtin_expect
. Więc to powinien być tylko komentarz. Ale to nie jest fałszywe, więc wycofałem swój głos przeciw.__builtin_expect
do trywialnego stworzenia przypadku testowego, który możesz zmierzyć, aperf stat
który będzie miał bardzo wysoki współczynnik błędnych przewidywań gałęzi. Ma to tylko wpływ na układ gałęzi . A tak przy okazji, Intel od czasów Sandybridge lub przynajmniej Haswell nie używa zbyt dużo / wcale statycznych prognoz; w BHT zawsze jest jakieś przewidywanie, czy jest to nieaktualny alias, czy nie. xania.org/201602/bpu-part-twoPrawidłowy sposób definiowania prawdopodobnych / mało prawdopodobnych makr w C ++ 11 jest następujący:
Ta metoda jest zgodna ze wszystkimi wersjami C ++, w przeciwieństwie do
[[likely]]
, ale opiera się na niestandardowym rozszerzeniu__builtin_expect
.Gdy te makra są zdefiniowane w ten sposób:
Może to zmienić znaczenie
if
instrukcji i złamać kod. Rozważ następujący kod:I jego wynik:
Jak widać, definicja PRAWDOPODOBNEGO używania
!!
jako rzutowaniabool
łamie semantykęif
.Nie o to chodzi
operator int()
ioperator bool()
powinno być powiązane. Co jest dobrą praktyką.Raczej używanie
!!(x)
zamiaststatic_cast<bool>(x)
traci kontekst dla konwersji kontekstowych C ++ 11 .źródło
switch
, dzięki. Konwersja kontekstowa, o której tu mowa,bool
jest częściowo związana z typem i pięcioma określonymi kontekstami tam wymienionymi , które nie obejmująswitch
kontekstu.(_Bool)(condition)
, ponieważ C nie ma przeciążenia operatora.(condition)
nie!!(condition)
. Oba sątrue
po zmianie tego (testowane z g ++ 7.1). Czy możesz skonstruować przykład, który faktycznie zademonstruje problem, o którym mówisz, używając funkcji!!
booleanize?Ponieważ wszystkie inne odpowiedzi odpowiednio zasugerowały, możesz użyć,
__builtin_expect
aby dać kompilatorowi wskazówkę, jak zorganizować kod asemblera. Jak wskazują oficjalne dokumenty , 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żywać rzeczywistych danych profilu do optymalizacji kodu, zamiast zgadywać.W podobny sposób, ale jeszcze nie wspomniano, znajduje się specyficzny dla GCC sposób wymuszenia na kompilatorze generowania kodu na „zimnej” ścieżce. Obejmuje to użycie atrybutów
noinline
icold
, które robią dokładnie to, na co wyglądają. Te atrybuty można zastosować tylko do funkcji, ale w C ++ 11 można deklarować wbudowane funkcje lambda, a te dwa atrybuty można również zastosować do funkcji lambda.Chociaż nadal należy to do ogólnej kategorii mikrooptymalizacji, a zatem ma zastosowanie standardowa rada - test nie zgaduj - wydaje mi się, że jest ona bardziej użyteczna niż
__builtin_expect
. Prawie żadne generacje procesorów x86 nie używają wskazówek dotyczących przewidywania rozgałęzień ( odniesienie ), więc jedyną rzeczą, na którą i tak będziesz mógł wpłynąć, jest kolejność kodu asemblera. Ponieważ wiesz, co to jest kod obsługi błędów lub kod „przypadków skrajnych”, możesz użyć tej adnotacji, aby upewnić się, że kompilator nigdy nie przewidział do niego gałęzi i połączy go z „gorącym” kodem podczas optymalizacji pod kątem rozmiaru.Przykładowe użycie:
Co więcej, GCC automatycznie zignoruje to na korzyść opinii o profilu, gdy jest dostępna (np. Podczas kompilacji
-fprofile-use
).Zobacz oficjalną dokumentację tutaj: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
źródło
__builtin_expect
robi. To wcale nie jest bezużyteczne. Masz rację, żecold
atrybut jest również przydatny, ale__builtin_expect
myślę , że nie doceniasz użyteczności .__builtin_expect może służyć do wskazania kompilatorowi, w którą stronę ma iść gałąź. Może to wpłynąć na sposób generowania kodu. Typowe procesory sekwencyjnie uruchamiają kod szybciej. Więc jeśli piszesz
kompilator wygeneruje kod podobny do
Jeśli twoja wskazówka jest poprawna, spowoduje to wykonanie kodu bez żadnych faktycznie wykonanych gałęzi. Będzie działać szybciej niż normalna sekwencja, w której każda instrukcja if będzie rozgałęziać się wokół kodu warunkowego i wykona trzy gałęzie.
Nowsze procesory x86 mają instrukcje dotyczące gałęzi, które mają zostać pobrane, lub dla gałęzi, które mają nie zostać pobrane (istnieje przedrostek instrukcji; nie jestem pewien szczegółów). Nie jestem pewien, 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żesz faktycznie wpłynąć na przewidywanie gałęzi .
źródło
Jeśli chodzi o 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, co robi to, co mówią inni. Ponadto myślę, że nie chcesz mówić procesorowi, czy gałąź jest zajęta, czy nie zawsze . Dzisiejsze procesory, takie jak architektura Intela, potrafią rozpoznawać dość złożone wzorce i skutecznie się dostosowywać.
Jednak są chwile, kiedy chcesz przejąć kontrolę nad tym, czy domyślnie przewiduje się, że gałąź jest brana, czy nie: Kiedy 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ądzający będzie działał wyjątkowo, ale być może gdy wystąpi, wymagana jest maksymalna wydajność (może wystąpić błąd krytyczny, aby zająć się jak najszybciej), dlatego warto kontrolować domyślną prognozę.
Inny przykład: możesz sklasyfikować swoje dane wejściowe i przejść do kodu, który obsługuje wynik Twojej klasyfikacji. Jeśli istnieje wiele klasyfikacji, procesor może zbierać statystyki, ale je tracić, ponieważ ta sama klasyfikacja nie następuje wystarczająco szybko, a zasoby prognozowania są przeznaczone na ostatnio wywołany kod. Chciałbym, żeby było prymitywne powiedzenie procesorowi „proszę nie poświęcać zasobów prognozowania temu kodowi”, tak jak czasami można powiedzieć „nie buforuj tego”.
źródło