2015-07-21 9 views
5

Trywialny funkcja Mam kompilacji z gcc i brzękiem:Dlaczego llvm i gcc używają różnych prologów funkcji na x86 64?

void test() { 
    printf("hm"); 
    printf("hum"); 
} 


$ gcc test.c -fomit-frame-pointer -masm=intel -O3 -S 

sub rsp, 8 
.cfi_def_cfa_offset 16 
mov esi, OFFSET FLAT:.LC0 
mov edi, 1 
xor eax, eax 
call __printf_chk 
mov esi, OFFSET FLAT:.LC1 
mov edi, 1 
xor eax, eax 
add rsp, 8 
.cfi_def_cfa_offset 8 
jmp __printf_chk 

I

$ clang test.c -mllvm --x86-asm-syntax=intel -fomit-frame-pointer -O3 -S  

# BB#0: 
push rax 
.Ltmp1: 
.cfi_def_cfa_offset 16 
mov edi, .L.str 
xor eax, eax 
call printf 
mov edi, .L.str1 
xor eax, eax 
pop rdx 
jmp printf     # TAILCALL 

Różnica Jestem zainteresowany jest gcc używa sub rsp, 8/add rsp, 8 dla funkcji prolog i clang używa push rax/pop rdx.

Dlaczego kompilatory używają różnych prologów funkcji? Który wariant jest lepszy? push i pop z pewnością koduje krótsze instrukcje, ale czy są one szybsze czy wolniejsze niż add i sub?

Powodem, dla którego chodzi przede wszystkim o manipulowanie stertami, wydaje się, że abi wymaga, aby rsp miał 16 bajtów wyrównanych dla procedur bez liści. Nie mogłem znaleźć żadnych flag kompilatora, które je usunęły.

Sądząc z odpowiedzi, wygląda na to, że pop jest lepszy. push rax + pop rdx = 1 + 1 = 2 vs. sub rsp, 8 + add rsp, 8 = 4 + 4 = 8. Tak więc pierwsza para oszczędza 6 bajtów bez żadnych kosztów.

+0

Jest to kwestia wyboru. Trudno powiedzieć, który wariant jest lepszy. Prawdopodobnie oba warianty są dość podobne pod względem wydajności. –

+0

re: twoja edycja. Tak, ABI gwarantuje, że przy wprowadzaniu funkcji "(% rsp + 8)" jest wyrównane do 16B. (Poprawiłem większość tego komentarza w mojej odpowiedzi). –

Odpowiedz

8

Intel, sub/add uruchomi silnik stosu wstawić dodatkowy uop, aby zsynchronizować %rsp dla części wykonawczej wykonanej poza kolejnością. (Patrz Agner Fog's microarch doc konkretnie pg 91, o silniku stosu. AFAIK, to nadal działa tak samo na Haswell jak na Pentium M, w miarę gdy musi wstawić dodatkowych UOPs.

push/pop zajmie mniej skondensowane -domain ups, a więc prawdopodobnie będzie bardziej wydajne, nawet jeśli używają portów store/load.Należy między nimi:

Tak więc, push/pop przynajmniej nie będzie wolniejsze, ale pobiera mniej instrukcji Lepsza gęstość I-cache jest dobra

BTW, myślę, że punktem pary insynuacji jest utrzymanie wyrównanego stosu 16B ter call przesuwa adres zwrotny 8B. Jest to jeden przypadek, w którym ABI kończy się wymaganiem instrukcji pół-bezużytecznych. Bardziej skomplikowane funkcje, które wymagają trochę miejsca na stos, aby rozlać locals, a następnie załadować je ponownie po wywołaniu funkcji, zwykle będą wymagały zarezerwowania miejsca.

SystemV (Linux) amd64 ABI gwarantuje, że przy wprowadzaniu funkcji, (%rsp + 8), gdzie argumenty na stosie będą, jeśli takie istnieją, będą wyrównane 16B. (http://x86-64.org/documentation/abi.pdf). Musisz to zorganizować w przypadku każdej funkcji, którą wywołujesz, lub jest to twoja wina, jeśli ulegają uszkodzeniu dzięki użyciu wyrównanego obciążenia SSE. Lub w inny sposób rozbić się na założeniu, w jaki sposób można użyć AND do maskowania adresu lub czegoś.

+0

Tak, chodzi tylko o wyrównanie stosu. – WhatsUp

+1

Należy również pamiętać, że większość funkcji czasu przydziela trochę miejsca na zmienne lokalne, a wariant 'sub' jest w tym przypadku bardziej wydajny. Prawdopodobnie twórcy kompilacji nie optymalizowali przypadku, gdy nie są potrzebni miejscowi. – Jester

+0

Tak, nie-liściowe funkcje z bardzo małą liczbą mieszkańców jest rzadkim przypadkiem. Myślę, że użycie przez Clanga 'pchania' /' pop' danych, o które nie ma nic wspólnego, to czysta optymalizacja. –

1

Zgodnie z eksperymentami, które zrobiłem na moim komputerze, push/pop mają tę samą prędkość, co add/sub. Sądzę, że tak powinno być w przypadku wszystkich mordern komputerów.

Zresztą różnica (jeśli w ogóle) jest naprawdę mikro-scopic, więc proponuję założyć, że są one równoważne ...

+0

Jaki rodzaj eksperymentu? Czy testowałeś coś, co było wąskie w przypadku przepustowości uop? Zgadzam się, że przez większość czasu nie ma chyba żadnej różnicy. –

+0

Zrobiłem najbardziej naiwną rzecz: skopiuj instrukcję kilka tysięcy razy (używając makr), włóż całość w pętlę i uciekaj. Nie jestem pewien, czy jest to wąskie gardło w uop. Czy mógłbyś potwierdzić? – WhatsUp

+0

'add' z tymi samymi rejestrami za każdym razem potrzebuje wyjścia poprzedniego jako wejścia, powodując opóźnienie limiter. 'add' ma przepustowość 3 na cykl na SnB/IvB i 4 na cykl na Haswell, jeśli są niezależne. 'push' może utrzymać 1/cykl,' pop' 2/cykl. Jak zawsze w przypadku współczesnych procesorów, ważny jest kontekst (jakie inne gatunki konkurują o zasoby wykonawcze i jak pasuje do łańcucha zależności). –

Powiązane problemy