2009-07-16 13 views
39

Są dwa dobrze znane sposoby ustawiania rejestru liczb całkowitych na zero na x86.Czy użycie xor reg, reg daje przewagę nad mov reg, 0?

Albo

mov reg, 0 

lub

xor reg, reg 

Istnieje opinia, że ​​drugi wariant jest lepszy ponieważ wartość 0 nie jest zapisany w kodzie i że oszczędza kilka bajtów wytwarzanego kodu maszynowego. Jest to zdecydowanie dobre - mniej pamięci podręcznej instrukcji jest używane, co czasami pozwala na szybsze wykonanie kodu. Wiele kompilatorów tworzy taki kod.

Jednak istnieje formalna zależność między instrukcjami między instrukcją xor i dowolną wcześniejszą instrukcją, która zmienia ten sam rejestr. Ponieważ istnieje depedencja, ta ostatnia instrukcja musi poczekać, aż poprzednia zakończy, a to może zmniejszyć obciążenie procesorów i obniżyć wydajność.

add reg, 17 
;do something else with reg here 
xor reg, reg 

Jest oczywiste, że wynik xor będzie dokładnie taki sam, niezależnie od początkowej wartości rejestru. Ale czy procesor jest w stanie to rozpoznać?

Próbowałem następujący test w VC++ 7:

const int Count = 10 * 1000 * 1000 * 1000; 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int i; 
    DWORD start = GetTickCount(); 
    for(i = 0; i < Count ; i++) { 
     __asm { 
      mov eax, 10 
      xor eax, eax 
     }; 
    } 
    DWORD diff = GetTickCount() - start; 
    start = GetTickCount(); 
    for(i = 0; i < Count ; i++) { 
     __asm { 
      mov eax, 10 
      mov eax, 0 
     }; 
    } 
    diff = GetTickCount() - start; 
    return 0; 
} 

Dzięki optymalizacji off obu pętlach wziąć dokładnie ten sam czas. Czy to rozsądnie udowadnia, że ​​procesor rozpoznaje, że nie ma zależności od wcześniejszej instrukcji 0123-? Jaki może być lepszy test, aby to sprawdzić?

+2

Myślę, że właśnie dlatego używamy języków wysokiego poziomu. Jeśli naprawdę chcesz wiedzieć, po prostu zmień etap kodegena, aby wykonać jedną lub drugą. Reper. Wybierz najlepsze. – jrockway

+3

ah, stara reguła 'xor reg, reg' - stare dobre czasy :) –

+1

Myślę, że architektura x86 jawnie definiuje XOR reg, reg jako łamiąc zależność od reg. Zobacz podręcznik architektury Intel. Spodziewałbym się MOV reg, ... zrobić to samo po prostu dlatego, że jest MOV. Zatem twoim prawdziwym wyborem jest to, który z nich zajmuje mniej miejsca (zgaduję, że czas wykonania jest taki sam), jeśli nie zależy ci na bitach statusu (XOR niszczy je wszystkie). –

Odpowiedz

25

rzeczywista odpowiedź dla Ciebie:

Intel 64 and IA-32 Architectures Optimization Reference Manual

Sekcja 3.5.1.8, gdzie chcesz wyglądać.

W skrócie są sytuacje, w których preferowane może być XOR lub MOV. Zagadnienia koncentrują się wokół łańcuchów zależności i zachowania kodów warunków.

+0

To nie brzmi jak cytowany tekst zaleca korzystanie z MOV w każdej sytuacji. – mwfearnley

+0

@mwfearnley Niestety Addison zdecydował się edytować moją odpowiedź, a czereśnia wybrał podzbiór treści, nie jest jasne, dlaczego tak się stało. Powinieneś przeczytać pełne dokumenty dotyczące sytuacji, w których preferowany jest mov. – Mark

+0

Dzięki za wyjaśnienie. Wydaje mi się, że była to próba uniknięcia problemu z poruszaniem się/zmianą dokumentu, ale niestety cytat nie zawierał wszystkich punktów, których potrzebował. Widzę teraz, że z tej sekcji mówi się, żeby używać MOV, gdy chcesz uniknąć ustawianie kodów warunków. – mwfearnley

2

Myślę, że na wcześniejszych architekturach instrukcja mov eax, 0 zajmowała nieco więcej czasu niż xor eax, eax ... nie pamiętam dokładnie dlaczego. Chyba że masz dużo więcej, ale wyobrażam sobie, że prawdopodobnie nie spowoduję chybienia pamięci podręcznej z powodu tego jednego literału zapisanego w kodzie.

Należy również pamiętać, że z poziomu pamięci status flag nie jest identyczny między tymi metodami, ale może to być błędne.

12

Przestałem być w stanie naprawić własne samochody po tym, jak sprzedałem mój kombi z 1966 roku. Podobnie jest z nowoczesnymi procesorami :-)

To naprawdę zależy od mikrokodu lub obwodów znajdujących się pod nim. Jest całkiem możliwe, że CPU może rozpoznać "XOR Rn,Rn" i po prostu wyzerować wszystkie bity bez martwienia się o zawartość. Ale oczywiście może zrobić to samo z "MOV Rn, 0". Dobry kompilator wybierze najlepszy wariant dla platformy docelowej, więc jest to zwykle problem tylko wtedy, gdy kodujesz w asemblerze.

Jeśli procesor jest na tyle inteligentny, swoją zależność XOR znika ponieważ wie wartość jest nieistotna i ustawić go w każdym razie zero (znowu zależy to od danego CPU jest używany).

Jednak już dawno temu dbałem o kilka bajtów lub kilka cykli zegara w moim kodzie - wygląda na to, że mikrooptymalizacja oszalała.

+3

Bez względu na to, czy jest to nadmierna optymalizacja do praktycznego zastosowania, może być przydatna zrozumienie, że nie wszystkie podobne instrukcje są sobie równe. ;) – jerryjvl

+3

@jerryjvl - Warto również zdać sobie sprawę, że nowoczesne procesory x86 na komputerach stacjonarnych nie uruchamiają kodu maszynowego x86 - dekodują x86 w RISC jak wewnętrzne instrukcje do wykonania. W związku z tym mogą rozpoznawać typowe sekwencje kodu (np. Xor eax, eax) i tłumaczyć je na prostsze instrukcje, np. Zamiast instrukcji "clear reg". Rzeczywisty xor prawdopodobnie nie jest wykonywany w tym przypadku. – Michael

+0

mikrooptymalizacja może wymagać zwariowania podczas pisania MBR =). – brianmearns

-8

Jak zauważyli inni, odpowiedź brzmi: "kogo to obchodzi?". Czy piszesz kompilator?

A w drugiej notatce, twoja analiza porównawcza prawdopodobnie nie zadziała, ponieważ masz tam oddział, który prawdopodobnie i tak zajmuje cały czas. (chyba że Twój kompilator rozwinie dla ciebie pętlę)

Innym powodem, dla którego nie można porównywać pojedynczych instrukcji w pętli, jest to, że cały twój kod zostanie zbuforowany (w przeciwieństwie do prawdziwego kodu). Więc wziąłeś dużą różnicę wielkości pomiędzy mov eax, 0 i xor eax, eax na zdjęciu, przez cały czas przechowywany w pamięci podręcznej L1.

Domyślam się, że wszelkie mierzalne różnice w wydajności w realnym świecie wynikałyby z różnicy wielkości, która pochłania pamięć podręczną, a nie z powodu czasu wykonania dwóch opcji.

+9

Cała ta strona ma "kogo to obchodzi" dla reszty świata. Nie sądzę, że byłaby to dobra odpowiedź. –

9

x86 ma instrukcje o zmiennej długości. MOV EAX, 0 wymaga jednego lub dwóch dodatkowych bajtów w przestrzeni kodu niż XOR EAX, EAX.

+5

'mov eax, 0' to 5 bajtów: jeden dla opcode' mov eax, imm32' i 4 dla 4B natychmiastowych danych. 'xor eax, eax' to 2 bajty: jeden kod' xor r32, r/m32', jeden dla operandów. –

6

Na nowoczesnych procesorach preferowany jest układ XOR. Jest mniejszy i szybszy.

Mniejszy tak naprawdę ma znaczenie, ponieważ w przypadku wielu rzeczywistych obciążeń jednym z głównych czynników ograniczających wydajność jest chybianie i-cache. Nie zostałoby to uchwycone w mikro-benchmarkach porównujących obie opcje, ale w świecie rzeczywistym sprawi, że kod będzie działał nieco szybciej.

I, ignorując zmniejszone chybianie i-cache, XOR na dowolnym CPU w ciągu ostatnich wielu lat jest taka sama lub szybsza niż MOV. Co może być szybsze niż wykonanie instrukcji MOV? W ogóle nie wykonuje żadnych instrukcji! Na najnowszych procesorach Intel logika wysyłki/zmiany nazwy rozpoznaje wzór XOR, "realizuje", że wynik będzie wynosił zero, i po prostu wskazuje rejestr w fizycznym rejestrze zerowym. Następnie wyrzuca instrukcję, ponieważ nie ma potrzeby jej wykonywania.

Wynik netto jest taki, że wzór XOR wykorzystuje zasoby zerowego wykonania i może, na ostatnich procesorach Intela, "wykonać" cztery instrukcje na cykl. MOV nakłada na siebie trzy instrukcje na cykl.

Szczegółowe zobaczyć ten wpis na blogu, że napisałem:

https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/

Większość programistów nie należy martwić się o to, ale twórcy kompilatora trzeba się martwić, i to jest dobre zrozumienie kodu, który jest generowane, a to po prostu cholernie fajne!

Powiązane problemy