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
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
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żywaj xor 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żyj xor r10d,r10d
, niexor r10,r10
.
Przykłady liczb całkowitych GP:
xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0
xor edx, edx ; RDX = 0
; small code-size alternative: cdq ; zero RDX if EAX is already zero
; SUB-OPTIMAL
xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont
xor r10,r10 ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes
and eax, 0 ; false dependency. (Microbenchmark experiments might want this)
sub eax, eax ; same as xor on most but not all CPUs; bad on Silvermont for example.
xor al, al ; false dep on some CPUs, not a zeroing idiom. Use xor eax,eax
mov al, 0 ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
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
, ale xorps
wymaga wykonania portu 5 na Intel Nehalem, a pxor
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
i pxor
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
# Good:
xorps xmm0, xmm0 ; smallest code size (for non-AVX)
pxor xmm0, xmm0 ; costs an extra byte, runs on any port on Nehalem.
xorps xmm15, xmm15 ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX. Code-size is the only penalty.
# Good with AVX:
vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0
vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix
#sub-optimal AVX
vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix because of high source reg
vpxor ymm0, ymm0, ymm0 ; decodes to 2 uops on AMD before Zen2
# Good with AVX512
vpxor xmm15, xmm0, xmm0 ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth using only high regs to avoid needing vzeroupper in short functions.
# Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction.
# sub-optimal with AVX512 (even without AVX512VL)
vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop. Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
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 / AVX vpcmpeqd
na wielu z nich załamuje depresję (chociaż nadal potrzebuje uop, aby zapisać jedynki), ale AVX512 vpternlogd
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.
Niektóre procesory rozpoznają sub same,same
jako idiom zerowania xor
, ale wszystkie procesory, które rozpoznają jakiekolwiek idiomy zerowania, rozpoznająxor
. Po prostu użyj xor
, abyś nie musiał się martwić, który procesor rozpoznaje który idiom zerowania.
xor
(w przeciwieństwie do tego, że jest uznanym idiomem zerowania mov 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ą jak xor
. 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ą do xor
pokonania mov
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ęc xor eax, eax
/ inc al
/ inc eax
unika zwykły kary częściowego Rejestrze, że pre-IVB Procesory mają. Nawet bez xor
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):
Procesor rozpoznaje XOR rejestru ze sobą, ustawiając go na zero. Specjalny znacznik w rejestrze pamięta, że wysoka część rejestru jest równa zero, tak że EAX = AL. Ten tag jest zapamiętywany nawet w pętli:
; Example 7.9. Partial register problem avoided in loop xor eax, eax mov ecx, 100 LL: mov al, [esi] mov [edi], eax ; No extra uop inc esi add edi, 4 dec ecx jnz LL
(od strony 82): Procesor pamięta, że górne 24 bity EAX są równe zeru, o ile nie otrzymujesz przerwania, błędnego przewidywania lub innego zdarzenia serializacji.
Strona 82 tego przewodnika również potwierdza, że niemov 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:
...
call some_func
xor ecx,ecx ; zero *before* the test
test eax,eax
setnz cl ; cl = (some_func() != 0)
add ebx, ecx ; no partial-register penalty here
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 chcesz xor
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żaj sahf
/ lahf
lub pushf
/ 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łe mov
instrukcje, więc movzx
pobiera jednostkę wykonawczą i ma niezerowe opóźnienie, co sprawia, że test / setcc
/ jest movzx
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żywanie mov reg, 0
/ setcc
do zerowania / łamania zależności jest prawdopodobnie najlepszą alternatywą, gdy xor
/ 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 zalet xor
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 gdy xor 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 to mov
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 pomijany mov
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 potem xor
-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.
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żywania xor
, ale czasami możesz xor-zero wyprzedzić coś, co ustawia flagi, jeśli masz zapasowy rejestr.
mov
-zero przed setcc
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.
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ącej popcnt/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.