W każdym języku programowania istnieją zestawy kodów polecanych w stosunku do innych. Próbowałem je tutaj wymienić w kolejności prędkości.
- Bitowe
- Dodawanie / odejmowanie liczb całkowitych
- Mnożenie / dzielenie liczb całkowitych
- Porównanie
- Kontrola przepływu
- Dodawanie / odejmowanie pływaka
- Float Multiplication / Division
Tam, gdzie potrzebujesz kodu o wysokiej wydajności, C ++ można ręcznie zoptymalizować w asemblerze, aby użyć instrukcji SIMD lub bardziej wydajnego przepływu sterowania, typów danych itp. Więc próbuję zrozumieć, czy typ danych (int32 / float32 / float64) lub wykonywanej operacji ( *
, +
, &
) wpływa na wydajność na poziomie centralnej.
- Czy pojedynczy procesor jest wielokrotnie wolniejszy niż procesor dodatkowy?
- W teorii MCU dowiadujesz się, że szybkość kodów zależy od liczby cykli procesora potrzebnych do wykonania. Czy to oznacza, że mnożenie zajmuje 4 cykle, a dodawanie zajmuje 2?
- Dokładnie jakie są charakterystyki prędkości podstawowych kodów matematyki i kontroli przepływu?
- Jeśli wykonanie dwóch kodów zajmie taką samą liczbę cykli, to oba będą mogły być używane zamiennie bez żadnego zwiększenia / utraty wydajności?
- Wszelkie inne szczegóły techniczne dotyczące wydajności procesora x86 są mile widziane
c++
performance
optimization
Robinicks
źródło
źródło
Odpowiedzi:
Przewodniki optymalizacji Agner Fog są doskonałe. Ma przewodniki, tabele czasów instrukcji i dokumenty na temat mikroarchitektury wszystkich najnowszych projektów procesorów x86 (sięgających wstecz aż do Pentium Intela). Zobacz także niektóre inne zasoby powiązane z /programming//tags/x86/info
Dla zabawy odpowiem na niektóre pytania (liczby z ostatnich procesorów Intel). Wybór operacji nie jest głównym czynnikiem optymalizacji kodu (chyba że można uniknąć podziału).
Tak (chyba że jest to siła 2). (3-4-krotne opóźnienie, z tylko jedną przepustowością na zegar w przypadku Intela). Nie wychodź jednak z drogi, aby tego uniknąć, ponieważ jest tak szybki, jak 2 lub 3 dodaje.
Zobacz tabele instrukcji Agner Fog i przewodnik po mikroarchitekturze, jeśli chcesz dokładnie wiedzieć : P. Uważaj na skoki warunkowe. Bezwarunkowe skoki (takie jak wywołania funkcji) mają niewielki narzut, ale niewiele.
Nie, mogą konkurować o ten sam port wykonawczy, co coś innego, lub nie. Zależy to od innych łańcuchów zależności, na których procesor może pracować równolegle. (W praktyce zazwyczaj nie podejmuje się żadnej przydatnej decyzji. Czasami pojawia się możliwość użycia przesunięcia wektorowego lub odtwarzania losowego wektorów, które działają na różnych portach procesorów Intel. Ale przesunięcie bajtów w całym rejestrze (
PSLLDQ
itp.) działa w jednostce losowej.)Dokumenty mikroarchy Agner Fog opisują potoki procesorów Intel i AMD na tyle szczegółowo, aby dokładnie określić, ile cykli powinna zająć pętla na iterację oraz czy wąskim gardłem jest wysoka przepustowość, łańcuch zależności lub rywalizacja o jeden port wykonawczy. Zobacz niektóre moje odpowiedzi na StackOverflow, takie jak ta lub ta .
Ponadto http://www.realworldtech.com/haswell-cpu/ (i podobne we wcześniejszych projektach) to fajna lektura, jeśli lubisz projektowanie procesorów.
Oto twoja lista posortowana według procesora Haswell na podstawie moich najlepszych gości. Jednak nie jest to naprawdę przydatny sposób myślenia o rzeczach do niczego poza dostrajaniem pętli asm. Efekty predykcji pamięci podręcznej / gałęzi zwykle dominują, więc napisz kod, aby miał dobre wzorce. Liczby są bardzo zmienne ręcznie i starają się uwzględnić wysokie opóźnienia, nawet jeśli przepustowość nie jest problemem, lub generować więcej błędów, które zapychają rurę, aby inne rzeczy działały równolegle. Esp. numery pamięci podręcznej / oddziału są bardzo wymyślne. Opóźnienia mają znaczenie dla zależności przenoszonych przez pętlę, przepustowość ma znaczenie, gdy każda iteracja jest niezależna.
TL: DR te liczby są tworzone na podstawie tego, co wyobrażam sobie dla „typowego” przypadku użycia, w zakresie kompromisów między opóźnieniem, wąskimi gardłami w portach wykonawczych i przepustowością front-endu (lub przeciągnięciami dla takich rzeczy jak brak gałęzi ). Proszę nie używać tych liczb do jakiejkolwiek poważnej analizy perf .
przesuwanie i obracanie (liczba stałych kompilacji w czasie) /
wersje wektorowe wszystkich z nich (1 do 4 na cykl, opóźnienie 1 cyklu)
tmp += 7
zamiast w pętlitmp = i*7
)sum
zmiennej. (Mógłbym to zważyć i fp mul tak niskie jak 1 lub tak wysokie jak 5 w zależności od przypadku użycia)._mm_insert_epi8
itp.)y = x ? a : b
, luby = x >= 0
) (test / setcc
lubcmov
)%
stała czasowa kompilacji (brak potęgi 2).PHADD
Dodawanie wartości w wektorze)Zrobiłem to całkowicie na podstawie domysłów . Jeśli coś wygląda nie tak, to dlatego, że myślałem o innym przypadku użycia lub o błędzie edycji.
Względny koszt rzeczy na procesorach AMD będzie podobny, z tym wyjątkiem, że mają szybsze przesuwacze liczb całkowitych, gdy liczba przesunięć jest zmienna. Procesory z rodziny AMD Bulldozer są oczywiście wolniejsze na większości kodów, z różnych powodów. (Ryzen jest całkiem dobry w wielu sprawach).
Pamiętaj, że naprawdę niemożliwe jest sprowadzenie rzeczy do jednowymiarowego kosztu . Oprócz błędów pamięci podręcznej i nieprzewidzianych oddziałów wąskim gardłem w bloku kodu może być opóźnienie, całkowita przepustowość uop (frontend) lub przepustowość określonego portu (port wykonania).
„Wolna” operacja, taka jak podział FP, może być bardzo tania, jeśli otaczający kod utrzymuje procesor zajęty inną pracą . (wektor FP div lub sqrt są po 1 uop, każdy ma po prostu złe opóźnienie i przepustowość. Blokują tylko jednostkę podziału, a nie cały port wykonania, na którym jest włączony. Div liczby całkowitej to kilka uops.) Więc jeśli masz tylko jeden podział FP za każde ~ 20 mul i dodanie, a CPU ma do wykonania inną pracę (np. niezależna iteracja w pętli), wtedy „koszt” div FP może być mniej więcej taki sam jak FP mul. Jest to prawdopodobnie najlepszy przykład czegoś, co ma niską przepustowość, gdy wszystko, co robisz, ale bardzo dobrze miesza się z innym kodem (gdy opóźnienie nie jest czynnikiem), z powodu niskiej całkowitej liczby błędów.
Zauważ, że dzielenie liczb całkowitych nie jest tak przyjazne dla otaczającego kodu: w Haswell jest 9 upops, z jednym na przepustowość 8-11c i opóźnieniem 22-29c. (Podział 64-bitowy jest znacznie wolniejszy, nawet na Skylake.) Tak więc opóźnienia i liczby przepustowości są nieco podobne do dzielenia FP, ale dzielenie FP jest tylko jednym zwiększeniem.
Aby zapoznać się z przykładami analizy krótkiej sekwencji insns pod kątem przepustowości, opóźnień i całkowitych błędów, zobacz niektóre z moich odpowiedzi SO:
sum += x[i] * y[i]
przez rozwinięcie wielu akumulatorów wektorowych w celu ukrycia opóźnienia FMA. Jest dość techniczny i niskopoziomowy, ale pokazuje rodzaj wyjścia w języku asemblera, który chcesz uzyskać od kompilatora, i dlaczego ma to znaczenie.IDK, jeśli inne osoby piszą odpowiedzi SO, w tym tego rodzaju analizy. O wiele łatwiej jest mi znaleźć własną, ponieważ wiem, że często zagłębiam się w ten szczegół i pamiętam, co napisałem.
źródło
To zależy od danego procesora, ale w przypadku nowoczesnego procesora lista wygląda mniej więcej tak:
W zależności od procesora może wystąpić znaczna opłata za pracę z 64-bitowymi typami danych.
Twoje pytania:
if
tego, co możesz rozsądnie zrobić z arytmetyką.I na koniec, jeśli tworzysz grę, nie przejmuj się tym zbytnio, lepiej skoncentruj się na tworzeniu dobrej gry niż przerywaniu cykli procesora.
źródło
Zrobiłem test operacji na liczbach całkowitych, który zapętlono milion razy na x64_64, doszedłem do krótkiego wniosku, jak poniżej,
dodaj --- 116 mikrosekund
sub ---- 116 mikrosekund
mul ---- 1036 mikrosekund
div ---- 13037 mikrosekund
powyższe dane już zmniejszyły narzut wywołany przez pętlę,
źródło
Instrukcje procesorów Intel można pobrać bezpłatnie ze strony internetowej. Są dość duże, ale technicznie mogą odpowiedzieć na twoje pytanie. W szczególności potrzebujesz instrukcji optymalizacji, ale instrukcja ma także czasy i opóźnienia dla większości głównych linii procesora dla instrukcji SIMD, ponieważ różnią się one od układu do układu.
Ogólnie uważam, że pełne gałęzie, a także ściganie za pomocą wskaźników (trawersy list linków, wywoływanie funkcji wirtualnych) są najlepsze w stosunku do perf killerów, ale procesory x86 / x64 są bardzo dobre w obu przypadkach, w porównaniu do innych architektur. Jeśli kiedykolwiek przeniesiesz się na inną platformę, zobaczysz, na ile poważny może być problem, jeśli piszesz kod o wysokiej wydajności.
źródło