Jaki jest najlepszy sposób ustawienia rejestru na zero w asemblerze x86: xor, mov czy i?

Odpowiedzi:

222

Podsumowanie TL; DR : xor same, sameto 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, r64jest stratą bajtu, ponieważ potrzebuje przedrostka REX.

Co gorsza, Silvermont rozpoznaje tylko xor r32,r32jako 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, xmmmoże mieć sens. Jest o jeden bajt krótszy niż pxor, ale xorpswymaga wykonania portu 5 na Intel Nehalem, a pxormoż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 xorpsi pxorsą 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, xmmjest dobrym wyborem do zerowania YMM (AVX1 / AVX2) lub ZMM (AVX512) lub dowolnego przyszłego rozszerzenia wektora. vpxor ymm, ymm, ymmnie 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..7maski AVX512 . SSE / AVX vpcmpeqdna wielu z nich załamuje depresję (chociaż nadal potrzebuje uop, aby zapisać jedynki), ale AVX512 vpternlogddla 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,samejako 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):

  • mniejszy rozmiar kodu niż mov reg,0. (Wszystkie procesory)
  • pozwala uniknąć kar za częściowe zarejestrowanie późniejszego kodu. (Rodzina Intel P6 i rodzina SnB).
  • nie używa jednostki wykonawczej, oszczędzając energię i zwalniając zasoby wykonawcze. (Rodzina Intel SnB)
  • mniejsze uop (brak natychmiastowych danych) pozostawia miejsce w linii pamięci podręcznej uop na pobliskie instrukcje do wypożyczenia w razie potrzeby. (Rodzina Intel SnB).
  • nie wykorzystuje wpisów w fizycznym pliku rejestru . (Przynajmniej rodzina Intel SnB (i P4), prawdopodobnie również AMD, ponieważ używają podobnego projektu PRF zamiast utrzymywać stan rejestru w ROB, jak mikroarchitektury z rodziny Intel P6).

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 xorjest 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 immediatedziała na tym samym EX0 / EX1 portów egzekucyjnym całkowitą jak xor. mov reg,regmoż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 xorpokonania movjest 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).

xorbędzie oznaczyć rejestru jako posiadające górne części wyzerowany , więc xor eax, eax/ inc al/ inc eaxunika zwykły kary częściowego Rejestrze, że pre-IVB Procesory mają. Nawet bez xorIvB 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.


xorustawia flagi , co oznacza, że ​​musisz być ostrożny podczas testowania warunków. Ponieważ setccjest 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, setea albo nie masz zapasowego rejestru, albo chcesz xorcał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/ setccoznaczałoby znaczną karę w przypadku starszych procesorów Intela i nadal byłoby nieco gorsze w przypadku nowszych Intel.

Używanie setcc/ movzx r32, r8jest 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/ lahflub 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 movinstrukcje, więc movzxpobiera jednostkę wykonawczą i ma niezerowe opóźnienie, co sprawia, że ​​test / setcc/ jest movzxgorszy 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/ movzxbez 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/ setccdo zerowania / łamania zależności jest prawdopodobnie najlepszą alternatywą, gdy xor/ test / setccnie 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.)


andz 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 xori 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,samena niektórych, ale nie wszystkich procesorach, podczas gdy xor same,samesą 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 movdziała). xorprzerywa tylko łańcuchy zależności w specjalnym przypadku, w którym src i dest są tym samym rejestrem, przez co jest pomijany movna 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.


TL: DR:

Jeśli naprawdę sprawia, że ​​twój kod jest ładniejszy lub zapisuje instrukcje, to na pewno zeruj, movaby 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 setccjest lepsze dla opóźnienia niż movzx reg32, reg8po (z wyjątkiem Intela, kiedy można wybrać różne rejestry), ale gorszy rozmiar kodu.

Peter Cordes
źródło
7
Większość instrukcji arytmetycznych OP R, S jest zmuszonych przez niesprawny procesor CPU do oczekiwania na wypełnienie zawartości rejestru R przez poprzednie instrukcje z rejestrem R jako celem; jest to zależność od danych. Kluczową kwestią jest to, że chipy Intel / AMD mają specjalny sprzęt do łamania zależności typu „must-wait-for-data” w rejestrze R, gdy napotkany zostanie XOR R, R i niekoniecznie robi to w przypadku innych instrukcji zerowania rejestrów. Oznacza to, że instrukcja XOR może zostać zaplanowana do natychmiastowego wykonania i dlatego Intel / AMD zaleca jej użycie.
Ira Baxter
3
@IraBaxter: Tak, i żeby uniknąć nieporozumień (ponieważ widziałem to błędne przekonanie na SO), mov reg, srcró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/tzcntna fałszywym dep na miejscu docelowym)
Peter Cordes,
2
@Zboson: „Opóźnienie” instrukcji bez żadnych zależności ma znaczenie tylko wtedy, gdy w potoku był bąbelek. Jest to przyjemne w przypadku eliminacji ruchu, ale w przypadku instrukcji zerowania korzyść z zerowego opóźnienia pojawia się dopiero po czymś takim jak błędne przewidzenie gałęzi lub przegapienie, gdy wykonanie oczekuje na zdekodowane instrukcje, a nie na gotowość danych. Ale tak, eliminacja mov nie czyni movdarmowym, 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.
Peter Cordes,
2
Według Agnera KNL nie rozpoznaje niezależności rejestrów 64-bitowych. Więc xor r64, r64nie tylko marnuje bajt. Jak mówisz, xor r32, r32to 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.
Bozon Z
3
ah, gdzie jest stary dobry MIPS, z jego „rejestrem zerowym”, kiedy go potrzebujesz.
hayalci