pliki prefetch
Transkrypt
pliki prefetch
Optymalizacja wykonania programów sekwencyjnych Krzysztof Banaś Obliczenia Wysokiej Wydajności 1 Optymalizacja sekwencyjna • Przez optymalizację rozumie się zmiany dokonywane w kodzie źródłowym lub w trakcie tłumaczenia kodu źródłowego na język maszynowy mające na celu osiągnięcie pożądanych cech przez program wynikowy • Optymalizacji kodu dokonuje się zazwyczaj ze względu na jeden z dwóch czynników: – rozmiar kodu – szybkość działania kodu (wydajność) Krzysztof Banaś Obliczenia Wysokiej Wydajności 2 Optymalizacja sekwencyjna • Efekt optymalizacji wydajności programów uzyskuje się najczęściej poprzez: – redukcję liczby wykonywanych operacji – optymalizację dostępu do pamięci – umożliwienie sprawniejszego przetwarzania potokowego przez procesor • Ten ostatni cel może być uzyskiwany np. w efekcie usunięcia pojawiających się w programach zależności danych Krzysztof Banaś Obliczenia Wysokiej Wydajności 3 Optymalizacja sekwencyjna • Optymalizację przeprowadzić można: – “ręcznie” stosując odpowiednie techniki – wykorzystując opcje optymalizującego kompilatora – przekazując, jeśli jest taka możliwość, wykonanie części kodu procedurom zoptymalizowanych bibliotek • Opłacalność wyboru jednego z powyższych sposobów zmienia się w czasie i zależy od szeregu czynników, takich jak np.: – istnienie i jakość zoptymalizowanych bibliotek – wiek i typowość sprzętu, na którym dokonywane są obliczenia – typowość optymalizowanego programu Krzysztof Banaś Obliczenia Wysokiej Wydajności 4 Optymalizacja sekwencyjna • Klasyczne sposoby optymalizacji obejmują szereg technik, które można stosować ręcznie i które realizowane są także przez optymalizujące kompilatory • Zyski ze stosowania poszczególnych technik zależą i od szczegółowej struktury kodu, i od własności procesora, na którym przeprowadzane są obliczenia • W korzystnych przypadkach optymalizacja sekwencyjna może prowadzić do kilkudziesięciokrotnego zmniejszenia czasu wykonania programów Krzysztof Banaś Obliczenia Wysokiej Wydajności 5 Klasyczne techniki optymalizacji • Optymalizacja dotycząca zmiennych i wyrażeń: – – – – – constant folding (zwijanie stałych) copy propagation (propagacja kopii) strength reduction (redukcja złożoności wyrażeń) variable renaming (przemianowanie zmiennych) common subexpression elimination (eliminacja powtarzających się podwyrażeń) Krzysztof Banaś Obliczenia Wysokiej Wydajności 6 Klasyczne techniki optymalizacji • Optymalizacja wykonania pętli: – induction variable simplification (uproszczenie wyrażeń zawierających indeks pętli) – loop invariant code motion (usunięcie poza pętle kodu niezależnego od iteracji) – loop interchange (zamiana kolejności wykonywania pętli) – loop fusion (łączenie pętli) – loop fission (rozdzielanie pętli) – loop unrolling (rozwijanie pętli) – blocking (grupowanie instrukcji ze względu na dostęp do pamięci podręcznej) • Przykład: mat_vec Krzysztof Banaś Obliczenia Wysokiej Wydajności 7 Klasyczne techniki optymalizacji • Optymalizacja na poziomie instrukcji: – dead code removal (usuwanie nieosiągalnego lub produkującego zbędne dane kodu) – tailrecursion elimination (eliminacja rekursji ogonowej) – inlining (wplatanie procedur – rozwijanie w miejscu wywołania) – software prefetching – pobieranie z wyprzedzeniem realizowane programowo – software pipelining – przetwarzanie potokowe na poziomie kodu źródłowego – i wiele innych • Przykład: ddot Krzysztof Banaś Obliczenia Wysokiej Wydajności 8 Kompilatory optymalizujące ➔ ➔ Optymalizacja w trakcie kompilacji odbywa się najczęściej po analizie składniowej, przed generowaniem kodu pośredniego (object code) Kompilator operuje na formie kodu przetworzonej przez analizator składni do jednej z możliwych postaci, takich jak: drzewa, kod dwuadresowy, kod trójadresowy, odwrotna notacja polska itp. Krzysztof Banaś Obliczenia Wysokiej Wydajności 9 Przykład postaci pośredniej while( j < n ) { k = k + 2j; m = 2j; j++; } Krzysztof Banaś A: t1 := j; t2 := n t3 := t1 < t2 jmp (B) t3 jmp (C) B: t4 := k t5 := j t6 := t5 * 2 t7 := t4 + t6 k := t7 t8 := j t9 := t8 * 2 m := t9 .... jmp (A) C: ... Obliczenia Wysokiej Wydajności 10 Kompilatory optymalizujące ➔ Analiza składniowa umożliwia nadanie programowi struktury, w której uwzględnia się: – przepływ sterowania: możliwości przekazania sterowania z jednego punktu kodu do drugiego – przepływ danych: określenie miejsca, w którym nadaje się wartość zmiennej i miejsc, w których się z tej wartości korzysta ➔ Analiza prowadzi do zapisu programu z użyciem: – rejestrów – bloków podstawowych Krzysztof Banaś Obliczenia Wysokiej Wydajności 11 Kompilatory optymalizujące • Blok podstawowy: – sekwencja instrukcji charakteryzująca się tym, że jeżeli wykonywana jest jedna z nich wykonywane są wszystkie – z wnętrza bloku podstawowego nie można wyskoczyć (instrukcja skoku jest zawsze końcem bloku) – do wnętrza bloku nie można wskoczyć (dowolna instrukcja opatrzona etykietą lub będąca celem skoku jest początkiem bloku podstawowego Krzysztof Banaś Obliczenia Wysokiej Wydajności 12 Przykłady kodu asemblera .L2 .L4 movl 4(%ebp), %eax cmpl 12(%ebp), %eax jl .L4 jmp .L3 // j > eax // n <> eax ? movl 4(%ebp), %eax movl %eax, %edx leal 0(,%edx,2), %eax addl %eax, 8(%ebp) movl 4(%ebp), %eax movl %eax, %edx leal 0(,%edx,2), %eax movl %eax, 16(%ebp) incl 4(%ebp) jmp .L2 // j> eax // j>edx // eax=2*edx // k += eax (k+=2*j) // j>eax // j>edx // eax=2*edx // m=eax (m=2*j) // j++ .L3 Krzysztof Banaś Obliczenia Wysokiej Wydajności 13 Przykłady kodu asemblera .L2 .L4 .L4 movl 4(%ebp), %eax cmpl 12(%ebp), %eax jl .L4 jmp .L3 // j > eax // n <> eax ? movl 4(%ebp), %eax movl %eax, %edx leal 0(,%edx,2), %eax addl %eax, 8(%ebp) movl 4(%ebp), %eax movl %eax, %edx leal 0(,%edx,2), %eax movl %eax, 16(%ebp) incl 4(%ebp) jmp .L2 // j> eax // j>edx // eax=2*edx wersja 2 (IVS): // k += eax // j>eax .L4: // j>edx addl $1, %ecx // eax=2*edx addl %eax, %edx // m=eax // j++ addl $2, %eax cmpl %r8d, %ecx jne .L4 .L3 Krzysztof Banaś leal (%edx, %eax, 2), %edx leal 0(,%eax,2), %ecx incl %eax cmpl %ebx, %eax jl .L4 Obliczenia Wysokiej Wydajności // edx+=2*eax // ecx=2*eax // eax+=1 // n<>eax ? // j++ // k+=m // m+=2 // n<>j ? 14 Kompilatory optymalizujące • Typowe opcje optymalizacji: – poziomy optymalizacji (grupowanie różnych technik ze względu na czas działania i agresywność – ingerencję w pierwotną strukturę kodu): zazwyczaj oznaczane O0,O1,itd. (najwyższe opcje oznaczają często zrównoleglenie kodu) – szczegółowe techniki optymalizacji (przykłady z gcc): • fstrengthreduce, fcsefollowjumps, ffastmath, funrollloops, fscheduleinsns, finlinefunctions, fomitframepointer • niektóre dostępne dla ręcznej optymalizacji, inne nie – wektoryzacja (wykorzystanie rozkazów SIMD) – zrównoleglenie (model z pamięcią wspólną, najczęściej poprzez uzupełnienie kodu dyrektywami OpenMP) Krzysztof Banaś Obliczenia Wysokiej Wydajności 15 Język asemblera AK • Przykład: lista rozkazów IA32 – transfer danych: • mov: przesunięcie (bez operacji pamięćpamięć) • push, pop: operacje na stosie; ld, st – load, store – operacje arytmetyczne i logiczne: • add, sub, mul: standardowe +, , * • inc, dec, neg: ++1, 1, *=(1) • xor, and, or: działania logiczne (na bitach) – leal: obliczenie adresu (bez transferu danych) – cmp: obliczenie wyrażenia warunkowego – przeniesienie sterowania • jmp, jge, je, jl: skok bezwarunkowy i warunkowe • call, ret: obsługa wywołań procedur Krzysztof Banaś Obliczenia Wysokiej Wydajności 16 Język asemblera AK • Przykład: lista rozkazów IA32 – argumenty: • bezpośrednie • zawartość rejestrów • zawartość komórek pamięci o obliczonym adresie – obliczanie adresu: • adres = base + index*scale + disp • notacja AT&T: disp(base,index,scale) • base i index są zawartościami rejestrów 32bitowych • disp i scale są liczbami (scale=1,2,4,8) Krzysztof Banaś Obliczenia Wysokiej Wydajności 17 Język asemblera AK ➔ Przykład: lista rozkazów IA32 dostępne rejestry 32bitowe: %eax (akumulator), %ebx, %ecx, ..., %ebp (wskaźnik ramki), %esp (wskaźnik stosu) 16bitowe: %ax (adresowanie połówki %eax), itd. 8bitowe: %ah, %al (adresowanie połówek %ax), itd. segmentowe: %cs (kod), %ds (dane), %ss (stos), itd. kontrolne, uruchamiania (debugowania), testowe zmiennoprzecinkowe w postaci stosu: %st(0), %st(1), itd. Krzysztof Banaś Obliczenia Wysokiej Wydajności 18 Lista rozkazów x86_64 AK ➔ Powszechne obecnie rdzenie 64bitowe ➔ Szereg optymalizacji ze względu na wydajność ➔ Standardowe rejestry 64bitowe Większa liczba rejestrów (16: %rax %r15 ) Argumenty (do 6 całkowitych i do 8 zmiennoprzecinkowych) procedur przekazywane poprzez rejestry Nowe rozkazy operacji całkowitych (końcówka q dla argumentów 64bitowych) i zmiennoprzecinkowych (końcówka sd) Rozbudowana liczba rozkazów wektorowych (operujących na 16 rejestrach 128 bitowych, %xmm00 – %xmm15 ) Wzrost stopnia komplikacji kodu asemblera Dużo rozkazów manipulacji zmiennymi (np. cltq a.k.a. cdqe zmiana liczby z 32 na 64bitową, inne zmieniające rozmiar danych) Krzysztof Banaś Obliczenia Wysokiej Wydajności 19 Język asemblera AK • Pliki w języku assemblera: – dwie notacje składni: firmy Intel i firmy AT&T – składnia AT&T (przyjęta w asemblerze GNU): • nazwa rozkazu zawiera jako ostatnią literę typ argumentu • źródło danych poprzedza cel wyniku • argumenty bezpośrednie poprzedzone są symbolem $ • nazwy rejestrów poprzedzone są znakiem % • osobne rozkazy procesora i koprocesora liczb zmiennopozycyjnych (te ostatnie zaczynają się literą f) Krzysztof Banaś Obliczenia Wysokiej Wydajności 20 Asembler GNU AK • Pliki asemblera GNU (standardowy asembler Linuxa) – pojedyncza linia pojedynczą instrukcją – instrukcja • etykieta: • .dyrektywa_asemblera • rozkaz_asemblera – dyrektywy określają: pliki (.file), sekcje (segmenty) (.section), stałe (napisy, liczby, znaki) (“...”, .ascii, .float,...), wyrównania (.align), symbole wspólne (.comm), itp. – rozkazy_asemblera odpowiadają rozkazom procesora Krzysztof Banaś Obliczenia Wysokiej Wydajności 21