Wszystkie poniższe instrukcje robią to samo: ustawiają %eax
na zero. Który sposób jest optymalny (wymagający najmniejszej liczby cykli maszyny)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
performance
assembly
optimization
x86
micro-optimization
balajimc55
źródło
źródło
Odpowiedzi:
Podsumowanie TL; DR :
xor same, same
to najlepszy wybór dla wszystkich procesorów . Żadna inna metoda nie ma nad nią żadnej przewagi i ma przynajmniej pewną przewagę nad jakąkolwiek inną metodą. Jest oficjalnie zalecany przez Intel i AMD oraz co robią kompilatory. W trybie 64-bitowym nadal używajxor r32, r32
, ponieważ zapisanie 32-bitowego rejestru zeruje górne 32 .xor r64, r64
jest stratą bajtu, ponieważ potrzebuje przedrostka REX.Co gorsza, Silvermont rozpoznaje tylko
xor r32,r32
jako zepsuty, a nie 64-bitowy rozmiar operandu. Dlatego nawet jeśli prefiks REX jest nadal wymagany, ponieważ zerujesz r8..r15, użyjxor r10d,r10d
, niexor r10,r10
.Przykłady liczb całkowitych GP:
Zerowanie rejestru wektorowego jest zwykle najlepiej wykonane
pxor xmm, xmm
. To zwykle robi gcc (nawet przed użyciem z instrukcjami FP).xorps xmm, xmm
może mieć sens. Jest o jeden bajt krótszy niżpxor
, alexorps
wymaga wykonania portu 5 na Intel Nehalem, apxor
może działać na dowolnym porcie (0/1/5). (Opóźnienie opóźnienia obejścia 2c Nehalema między liczbą całkowitą a FP zwykle nie ma znaczenia, ponieważ wykonanie poza kolejnością może zazwyczaj ukryć to na początku nowego łańcucha zależności).W mikroarchitekturach z rodziny SnB żaden rodzaj zerowania xor nie potrzebuje nawet portu wykonania. Na AMD i starszej niż Nehalem P6 / Core2 Intel
xorps
ipxor
są obsługiwane w ten sam sposób (jak instrukcje wektorowo-całkowite).Użycie wersji AVX 128b instrukcji wektorowej zeruje również górną część reg, więc
vpxor xmm, xmm, xmm
jest dobrym wyborem do zerowania YMM (AVX1 / AVX2) lub ZMM (AVX512) lub dowolnego przyszłego rozszerzenia wektora.vpxor ymm, ymm, ymm
nie zajmuje jednak żadnych dodatkowych bajtów do kodowania i działa tak samo na Intelu, ale wolniej na AMD przed Zen2 (2 uops). Zerowanie AVX512 ZMM wymagałoby dodatkowych bajtów (dla prefiksu EVEX), dlatego preferowane powinno być zerowanie XMM lub YMM.Przykłady XMM / YMM / ZMM
Zobacz: Czy zerowanie vxorps na AMD Jaguar / Bulldozer / Zen jest szybsze z rejestrami xmm niż ymm? a
jaki jest najskuteczniejszy sposób na wyczyszczenie jednego lub kilku rejestrów ZMM w Knights Landing?
Częściowo powiązane: Najszybszy sposób ustawienia wartości __m256 na wszystkie JEDNO bity i
wydajne ustawienie wszystkich bitów w rejestrze procesora na 1 obejmuje również rejestry
k0..7
maski AVX512 . SSE / AVXvpcmpeqd
na wielu z nich załamuje depresję (chociaż nadal potrzebuje uop, aby zapisać jedynki), ale AVX512vpternlogd
dla ZMM regs nie jest nawet załamywaniem depresyjnym . Wewnątrz pętli rozważ kopiowanie z innego rejestru zamiast ponownego tworzenia rejestrów z ALU uop, szczególnie z AVX512.Ale zerowanie jest tanie: xor-zerowanie xmm reg wewnątrz pętli jest zwykle tak samo dobre jak kopiowanie, z wyjątkiem niektórych procesorów AMD (Bulldozer i Zen), które mają eliminację mov dla regów wektorowych, ale nadal wymagają ALU uop do zapisywania zer dla xor -zerowanie.
Co jest specjalnego w zerowaniu idiomów, takich jak xor, na różnych łucznikach
Niektóre procesory rozpoznają
sub same,same
jako idiom zerowaniaxor
, ale wszystkie procesory, które rozpoznają jakiekolwiek idiomy zerowania, rozpoznająxor
. Po prostu użyjxor
, abyś nie musiał się martwić, który procesor rozpoznaje który idiom zerowania.xor
(w przeciwieństwie do tego, że jest uznanym idiomem zerowaniamov reg, 0
) ma kilka oczywistych i subtelnych zalet (lista podsumowująca, a następnie rozwinę je):mov reg,0
. (Wszystkie procesory)Mniejszy rozmiar kodu maszynowego (2 bajty zamiast 5) jest zawsze zaletą: większa gęstość kodu prowadzi do mniejszej liczby braków w pamięci podręcznej instrukcji oraz do lepszego pobierania instrukcji i potencjalnie dekodowania przepustowości.
Korzyści wynikające z nieużywania jednostki wykonawczej dla xor w mikroarchitekturach z rodziny Intel SnB są niewielkie, ale oszczędzają energię. Bardziej prawdopodobne jest, że będzie to miało znaczenie na SnB lub IvB, które mają tylko 3 porty wykonawcze ALU. Haswell i później mają 4 porty wykonawcze, które mogą obsługiwać całkowite instrukcje ALU, w tym
mov r32, imm32
, więc dzięki doskonałemu podejmowaniu decyzji przez harmonogram (co nie zawsze ma miejsce w praktyce), HSW może nadal wytrzymać 4 uops na zegar, nawet jeśli wszyscy potrzebują ALU porty wykonawcze.Zobacz moją odpowiedź na inne pytanie dotyczące zerowania rejestrów po więcej szczegółów.
Wpis na blogu Bruce'a Dawsona, do którego linkował Michael Petch (w komentarzu do pytania) wskazuje, że
xor
jest obsługiwany na etapie zmiany nazwy rejestru bez potrzeby jednostki wykonawczej (zero błędów w nieużywanej domenie), ale pominął fakt, że nadal jest to jeden uop w domenie połączonej. Nowoczesne procesory Intela mogą wydawać i wycofywać 4 UOPS połączonej domeny na zegar. Stąd pochodzą 4 zera na limit zegara. Zwiększona złożoność sprzętu do zmiany nazwy rejestru jest tylko jednym z powodów ograniczenia szerokości projektu do 4. (Bruce napisał kilka bardzo doskonałych postów na blogu, takich jak jego seria o matematyce FP i problemach z zaokrąglaniem x87 / SSE / zaokrąglania , które robię wysoce zalecane).Na procesorach AMD Bulldozer jednorodzinnych ,
mov immediate
działa na tym samym EX0 / EX1 portów egzekucyjnym całkowitą jakxor
.mov reg,reg
może również działać na AGU0 / 1, ale to tylko do kopiowania rejestrów, a nie do ustawiania z natychmiastowych. Tak więc, AFAIK, na AMD jedyną zaletą doxor
pokonaniamov
jest krótsze kodowanie. Może również zaoszczędzić fizyczne zasoby rejestrów, ale nie widziałem żadnych testów.Uznane idiomy zerowania pozwalają uniknąć kar częściowego rejestrowania na procesorach Intela, które zmieniają nazwy rejestrów częściowych oddzielnie od rejestrów pełnych (rodziny P6 i SnB).
xor
będzie oznaczyć rejestru jako posiadające górne części wyzerowany , więcxor eax, eax
/inc al
/inc eax
unika zwykły kary częściowego Rejestrze, że pre-IVB Procesory mają. Nawet bezxor
IvB potrzebuje scalenia UOP tylko wtedy, gdy wysokie 8bits (AH
) są modyfikowane, a następnie odczytywany jest cały rejestr, a Haswell nawet to usuwa.Z przewodnika mikroarchy Agner Fog, str. 98 (sekcja Pentium M, do której odwołują się późniejsze sekcje, w tym SnB):
Strona 82 tego przewodnika również potwierdza, że nie
mov reg, 0
jest rozpoznawany jako idiom zerowania, przynajmniej we wczesnych projektach P6, takich jak PIII lub PM. Byłbym bardzo zaskoczony, gdyby wydali tranzystory na wykrycie tego w późniejszych procesorach.xor
ustawia flagi , co oznacza, że musisz być ostrożny podczas testowania warunków. Ponieważsetcc
jest niestety dostępny tylko z miejscem docelowym 8-bitowym , zwykle musisz uważać, aby uniknąć kar za częściową rejestrację.Byłoby miło, gdyby x86-64 zmienił przeznaczenie jednego z usuniętych kodów operacyjnych (takich jak AAM) na 16/32/64 bit
setcc r/m
, z predykatem zakodowanym w 3-bitowym polu rejestru źródłowego pola r / m (sposób niektóre inne instrukcje z jednym operandem używają ich jako bitów kodu operacji). Ale oni tego nie zrobili, a to i tak nie pomogłoby w przypadku x86-32.Najlepiej byłoby użyć
xor
/ ustawić flagi /setcc
/ przeczytać pełny rejestr:Zapewnia to optymalną wydajność na wszystkich procesorach (bez blokad, łączenia błędów lub fałszywych zależności).
Sprawy są bardziej skomplikowane, gdy nie chcesz xorować przed instrukcją ustawiania flagi . np. chcesz rozgałęzić się na jednym warunku, a następnie ustawić cc na innym z tych samych flag. np.
cmp/jle
,sete
a albo nie masz zapasowego rejestru, albo chceszxor
całkowicie trzymać się z dala od niepobranej ścieżki kodu.Nie ma uznanych idiomów zerowania, które nie mają wpływu na flagi, więc najlepszy wybór zależy od docelowej mikroarchitektury. Na Core2 wstawienie scalającego UOP może spowodować przeciągnięcie 2 lub 3 cykli. Wydaje się, że na SnB jest tańsze, ale nie spędziłem dużo czasu na próbach pomiaru. Używanie
mov reg, 0
/setcc
oznaczałoby znaczną karę w przypadku starszych procesorów Intela i nadal byłoby nieco gorsze w przypadku nowszych Intel.Używanie
setcc
/movzx r32, r8
jest prawdopodobnie najlepszą alternatywą dla rodzin Intel P6 i SnB, jeśli nie możesz xor-zero przed instrukcją ustawiania flagi. To powinno być lepsze niż powtórzenie testu po zerowaniu xor. (Nawet nie rozważajsahf
/lahf
lubpushf
/popf
). IvB może wyeliminowaćmovzx r32, r8
(tj. Obsłużyć to przy zmianie nazwy rejestru bez jednostki wykonawczej lub opóźnień, jak zerowanie xor). Haswell i później tylko eliminują zwykłemov
instrukcje, więcmovzx
pobiera jednostkę wykonawczą i ma niezerowe opóźnienie, co sprawia, że test /setcc
/ jestmovzx
gorszy niżxor
/ test /setcc
, ale nadal jest co najmniej tak dobry jak test /mov r,0
/setcc
(i znacznie lepszy na starszych procesorach).Używanie
setcc
/movzx
bez zerowania w pierwszej kolejności jest złe w AMD / P4 / Silvermont, ponieważ nie śledzą one oddzielnie deprejestrów. Stara wartość rejestru byłaby fałszywa. Używaniemov reg, 0
/setcc
do zerowania / łamania zależności jest prawdopodobnie najlepszą alternatywą, gdyxor
/ test /setcc
nie jest opcją.Oczywiście, jeśli nie potrzebujesz
setcc
, aby wyjście było szersze niż 8 bitów, nie musisz niczego zerować. Uważaj jednak na fałszywe zależności na procesorach innych niż P6 / SnB, jeśli wybierzesz rejestr, który był ostatnio częścią długiego łańcucha zależności. (I uważaj na spowodowanie częściowego wstrzymania rejestracji lub dodatkowego uopu, jeśli wywołasz funkcję, która może zapisać / przywrócić rejestr, którego używasz.)and
z natychmiastowym zerem nie ma specjalnej wielkości liter, ponieważ jest niezależny od starej wartości na każdym znanym mi procesorze, więc nie przerywa łańcuchów zależności. Nie ma żadnych zaletxor
i wielu wad.Jest to przydatne tylko do pisania mikroznaków, gdy chcesz , aby zależność była częścią testu opóźnienia, ale chcesz utworzyć znaną wartość przez zerowanie i dodanie.
Zobacz http://agner.org/optimize/, aby uzyskać szczegółowe informacje dotyczące mikroarch , w tym, które idiomy zerowania są rozpoznawane jako łamiące zależności (np. Są
sub same,same
na niektórych, ale nie wszystkich procesorach, podczas gdyxor same,same
są rozpoznawane we wszystkich)mov
, przerywają łańcuch zależności od starej wartości rejestru (niezależnie od wartości źródła, zero czy nie, bo tak tomov
działa).xor
przerywa tylko łańcuchy zależności w specjalnym przypadku, w którym src i dest są tym samym rejestrem, przez co jest pomijanymov
na liście specjalnie rozpoznawanych przerywaczy zależności. (Ponadto, ponieważ nie jest rozpoznawany jako idiom zerowania, z innymi korzyściami, które niesie.)Co ciekawe, najstarszy projekt P6 (od PPro do Pentium III) nie rozpoznawał
xor
-zerowania jako przerywacza zależności, tylko jako idiom zerowania w celu uniknięcia opóźnień częściowego rejestru , więc w niektórych przypadkach warto było używać obu,mov
a potemxor
-zerowanie w tej kolejności, aby przerwać dep, a następnie ponownie zerować + ustawić wewnętrzny bit znacznika, tak aby górne bity były zerowe, więc EAX = AX = AL.Zobacz przykład Agner Fog 6.17. w jego mikroarch. pdf. Mówi, że dotyczy to również P2, P3, a nawet (wczesnego?) PM. Komentarz do posta na blogu, do którego prowadzi link, mówi, że to przeoczenie miało tylko PPro, ale testowałem na Katmai PIII, a @Fanael testowałem na Pentium M i obaj stwierdziliśmy, że nie złamało to zależności związanej z opóźnieniem -bound
imul
łańcucha. To niestety potwierdza wyniki Agner Fog.TL: DR:
Jeśli naprawdę sprawia, że twój kod jest ładniejszy lub zapisuje instrukcje, to na pewno zeruj,
mov
aby uniknąć dotykania flag, o ile nie wprowadzisz problemu z wydajnością innego niż rozmiar kodu. Unikanie flag typu clobbering jest jedynym rozsądnym powodem nieużywaniaxor
, ale czasami możesz xor-zero wyprzedzić coś, co ustawia flagi, jeśli masz zapasowy rejestr.mov
-zero przedsetcc
jest lepsze dla opóźnienia niżmovzx reg32, reg8
po (z wyjątkiem Intela, kiedy można wybrać różne rejestry), ale gorszy rozmiar kodu.źródło
mov reg, src
również przerywa łańcuchy dep dla procesorów OO (niezależnie od tego[mem]
, czy src to imm32 , czy inny rejestr). O tym łamaniu zależności nie wspomina się w podręcznikach optymalizacji, ponieważ nie jest to specjalny przypadek, który ma miejsce tylko wtedy, gdy src i dest są tym samym rejestrem. Dzieje się tak zawsze w przypadku instrukcji, które nie zależą od ich przeznaczenia. (z wyjątkiem implementacji Intela polegającejpopcnt/lzcnt/tzcnt
na fałszywym dep na miejscu docelowym)mov
darmowym, tylko zerowym opóźnieniem. Część „niepobranie portu wykonania” zwykle nie jest ważna. Przepustowość domeny połączonej może łatwo stanowić wąskie gardło, zwł. z ładunkami lub zapasami w mieszance.xor r64, r64
nie tylko marnuje bajt. Jak mówisz,xor r32, r32
to najlepszy wybór, szczególnie w przypadku KNL. Aby dowiedzieć się więcej, zobacz rozdział 15.7 „Specjalne przypadki niezależności” w tym podręczniku mikrarchii.