Jako kontynuację mojego pytania The advantages of using 32bit registers/instructions in x86-64, zacząłem mierzyć koszty instrukcji. Mam świadomość, że zostało to zrobione wiele razy (na przykład Agner Fog), ale robię to dla zabawy i samokształcenia.Powolna instrukcja jmp
Mój kod badania jest dość prosty (dla uproszczenia tutaj jako pseudo kod, w rzeczywistości w asemblerze):
for(outer_loop=0; outer_loop<NO;outer_loop++){
operation #first
operation #second
...
operation #NI-th
}
Ale jeszcze kilka rzeczy, które należy rozważyć.
- Jeśli wewnętrzna część pętli jest duża (duży
NI>10^7
) cała zawartość pętli nie pasuje do pamięci podręcznej instrukcji, a więc muszą być ładowane w kółko, dzięki czemu prędkość RAM zdefiniować czas potrzebny na wykonanie. Na przykład dla dużych części wewnętrznych,xorl %eax, %eax
(2 bajty) jest o 33% szybszy niżxorq %rax, %rax
(3 bajty). - Jeśli jest mały i cała pętla łatwo mieści się w pamięci podręcznej instrukcji, wówczas
xorl %eax, %eax
ixorq %rax, %rax
są równie szybkie i mogą być wykonywane 4 razy na cykl zegara.
Jednak ten prosty model nie zawiera wody dla instrukcji jmp
. Dla jmp
-instruction mój kod test wygląda następująco:
for(outer_loop=0; outer_loop<NO;outer_loop++){
jmp .L0
.L0: jmp .L1
L1: jmp L2
....
}
a wyniki:
- dla "dużych" rozmiarach pętli (już dla
NI>10^4
) I Działanie 4.2 ns/jmp
-instruction (oznaczałoby 42 bajty załadowane z pamięci RAM lub około 12 cykli zegara na moim komputerze). - Dla małych rozmiarów pętli (
NI<10^3
) Zmierzam instrukcję 1 ns/jmp-
(czyli około 3 cykli zegara, co wydaje się wiarygodne - tabele Agner Fog pokazują koszty 2 cykli zegara).
Instrukcja jmp LX
używa kodowania 2-bajtowego eb 00
.
Tak, moje pytanie: Co może być wyjaśnienie wysokiego kosztu jmp
-instrukcja w "dużych" pętli?
PS: Jeśli chcesz ją wypróbować na komputerze, można pobrać skrypty z here, wystarczy uruchomić sh jmp_test.sh
w src -folder.
Edit: wyniki doświadczalne potwierdzające teorię BTB rozmiarze Piotra.
Poniższa tabela przedstawia cykli na instrukcję dla różnych wartości ǸI
(względem NI
= 1000):
|oprations/ NI | 1000 | 2000| 3000| 4000| 5000| 10000|
|---------------------|------|------|------|------|------|------|
|jmp | 1.0 | 1.0 | 1.0 | 1.2 | 1.9 | 3.8|
|jmp+xor | 1.0 | 1.2 | 1.3 | 1.6 | 2.8 | 5.3|
|jmp+cmp+je (jump) | 1.0 | 1.5 | 4.0 | 4.4 | 5.5 | 5.5|
|jmp+cmp+je (no jump) | 1.0 | 1.2 | 1.3 | 1.5 | 3.8 | 7.6|
Jak widać:
- Dla instrukcji
jmp
, grupę (jeszcze nieznane) zasoby stają się rzadkie, a to prowadzi do spadku wydajności dlaǸI
większego niż 4000. - Ten zasób nie jest udostępniany z takim instruktorem jony jako
xor
- degradacja wydajności kopie nadal dlaNI
około 4000, jeślijmp
ixor
są wykonywane po sobie. - Ale ten zasób jest udostępniany
je
jeśli skok jest wykonany - najmp
+je
po drugim, zasób staje się rzadkością naNI
około 2000. - Jednak jeśli
je
nie skakać w ogóle, zasób staje skąpe po raz kolejny dlaNI
około 4000 (4 linia).
Matt Godbolt's branch-prediction reverse engineering articles ustala, że pojemność bufora docelowego oddziału wynosi 4096 pozycji. To jest bardzo mocny dowód na to, że brakujące wartości BTB są powodem obserwowanej różnicy przepustowości między małymi i dużymi pętlami jmp
.
Nazwy znajdują się w informacji debugowania. Wydanie plików wykonywalnych nie będzie mieć nazw etykiet w dowolnym miejscu. – doug65536
Zauważ, że 'xorq% rax,% rax' ma dokładnie to samo co' xorl% eax,% eax', więc prawie nigdy nie ma powodu, aby używać tego pierwszego (z wyjątkiem być może unikania wstawiania 'nop' dla wyrównania gdzieś). – fuz
Twoje "duże" 10.000 pętli instrukcji z łatwością zmieściłoby się w pamięci podręcznej L2 nowoczesnego procesora (256 KB), więc nie mierzysz prędkości pamięci RAM. –