Upload
lykhanh
View
215
Download
0
Embed Size (px)
Citation preview
Programowanie w asemblerzeOptymalizacja
Zbigniew Jurkiewicz, Instytut Informatyki UW
17 stycznia 2017
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Przesłania warunkowe
Czasem dokonujemy porównania i zaleznie od wynikuchcemy dokonac pojedynczego przesłania.Mozna wtedy uzyc instrukcji przesłania warunkowego,wykonywanego tylko gdy spełniony był wskazany warunek,np. instrukcja
cmove eax,1
umieszcza 1 w rejestrze eax tylko wtedy, gdyporównywane ostatnio elementy były równe.Podstawowa zaleta to unikanie koniecznosci oczyszczaniapotoku lub wykonania spekulacyjnego.Przypisanie warunkowe SETPrzepisanie warunkowe CMOV
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Przesłania warunkowe: przykład
Znalezienie wiekszej z dwóch liczb (w EAX i EBX, wynik wECX):
mov ecx,eaxcmp ebx,ecxcmova ecx,ebx
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Przesłania warunkowe: błedy
Załózmy, ze kompilujemy w C wyrazenie
int *xp;...return (xp ? *xp : 0);
Jesli xp jest w rdi, to mozna by uzyc
xor eax,eax ;Byc moze zwrócimy zerotest rdi,rdi ;xp == 0 ?cmovne eax,[rdi] ;Byc moze zwrócimy *xp
Ale wtedy dereferencja xp nastapi zawsze (nawet dlawskaznika NULL), a tego chcemy uniknac.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Unikanie skoków
Unikanie skoków to szerszy problem. Popatrzmy na obliczeniewartosci bezwzglednej
test eax,eax ;Ustawmy flagijns omin ;znak dodatnineg eax
omin:
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Unikanie skoków
Mozna to zrobic inaczej:
mov ecx,eaxsar ecx,31 ;wszedzie bit znakuxor eax,ecx ;odwracamy bitysub eax,ecx ;odejmujemy -1 i mamy uzupełnienie do 2
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Potega 2
Kolejna sztuczka: jak sprawdzic, czy liczba w EAX jest potegadwójki?
mov ebx,eax ;albo lea ebx,[eax - 1]dec ebxtest eax,ebxjnz niejest
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Podpowiedzi
Procesor próbuje zgadywac, czy skok warunkowy bedziewykonany.Przy statycznym przewidywaniu zakłada, ze skok „do tyłu”bedzie wykonany.Mozna mu podpowiadac uzywajac hintów: prefiksówHT(0x3e) i i HNT(0x2e), np.
test ecx,ecxdb 3eh ;HT = bedzie skokjz L9...
L9:
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Podpowiedzi
Czesto nie warto trzymac danych w pamieci buforowej, jesliuzywane jednorazowo
Instrukcje zapisu bezposredniego (non-temporal store)MOVNTI, MOVNTPD itp. podczas zapisu do pamieciomijaja cache.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Konserwatywnosc kompilatora
Kompilator C musi byc konserwatywny i generowac kodtak, aby obejmował wszystkie mozliwosciPrzykład:
void memclr (char *dane, int n) {for (; n > 0; n--)
*dane++ = 0;}
Gdyby kompilator wiedział cos o wyrównaniu dane,mógłby zerowac naraz po 2, 4 a nawet 8 bajtów.Musi jednak zakładac najgorszy przypadek.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Konserwatywnosc kompilatora
Istnieje kilka elementów C/++, które sa wzorcowymispowalniaczami.W czołówce jest konwersja (cast) z liczby rzeczywistej nacałkowita, np.
int i;float f;...i = (int)f;
Taka konwersja to 50-100 cykli. Powód: standard C /C++okresla inny sposób zaokraglania niz uzywany w FPU,wiec trzeba przełaczac tryb w koprocesorze.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Konserwatywnosc kompilatora
Inny kandydat do Oscara to pointer aliasing.W ponizszym kodzie kompilator nie wyciagnie obliczenia*p + 2 przed petle
void Func1 (int a[], int *p) {int i;for (i = 0; i < 100; i++)
a[i] = *p + 2;}
I słusznie, bo (niech zyje C i C++ :-)
void Func2() {int list[100];Func1(list, &list[8]);
}
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Konserwatywnosc kompilatora
Czasem recepty sa proste. Ponizszy kod dwukrotniepobiera arg1->p1 z pamieci:
struct S1 int p1;struct S2 int p2, p3;
void f1 (struct S1 *arg1, struct S2 *arg2)arg2->p2 += arg1->p1;arg2->p3 += arg1->p1;
Musi tak byc, bo arg2->p2 i arg1->p1 moga byc tasama komórka pamieci.A wystarczy wprowadzic zmienna lokalna i przypisac nania S1->p1.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Asembler
Asembler pozwala korzystac z dostepu do usług niskiegopoziomu:
Rejestry i bezposrednie wejscie/wyjscieOmijanie konwencji kompilatora: inne przekazywanieparametrów, naruszanie zasad przydziału pamieci,iteracyjne wołanie procedurŁaczenie niezgodnych fragmentów kodu, np. zbudowanychprzez inne kompilatoryReczna optymalizacja kodu w celu dopasowania do bardzokonkretnej konfiguracji sprzetowej
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Skrajny przykład
Dla nabrania apetytu
Ponizszy kod w C
float a[4], b[4], c[4];for (int i = 0; i < 4; i++) {c[i] = a[i] > b[i] ? a[i] : b[i];
}
mozna optymalnie zakodowac nastepujaco
movaps xmm0,[a] ;Load a vectormaxps xmm0,[b] ;max(a,b)movaps [c],xmm0 ;c = a > b ? a : b
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Gdy brakuje rejestrów czyli „dwa w jednym”
Mamy dwie zmienne indeks i przyrost, obie 16-bitowe(short)Mozna je włozyc do jednego rejestru ARM, indeks u góry.Wtedy kod w C
elem = tab[indeks];indeks += przyrost;
zapisuje sie w asemblerze jako
LDRB Relem, [Rtab, Rindprz, LSR#16]ADD Rindprz, Rindprz, Rindprz, LSL#16
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Intel/AMD
Repertuar instrukcji procesorów CISC (x86) nie jestoptymalny — potwierdzenie to kilkakrotne zmiany filozofiiarchitektury.Musi byc zachowany z uwagi na wsteczna kompatybilnoscz systemami lat 1980, gdy pamiec RAM i dyskowa byłymałe i kosztowne.Ale CISC o dziwo ma takze zalety. Zwiezłosc kodu dobrzepasuje do wymogów pamieci buforowych (cache) oograniczonych rozmiarach.Główny problem procesorów x86 to mała liczba rejestrów,troche poprawiony przy projektowaniu x86-64.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Akceleratory grafiki
Wymagajace aplikacje graficzne potrzebuja platformy zkoprocesorem do obsługi grafiki lub karta akceleratora.Moc obliczeniowa tam zawarta mozna wykorzystac takzedo innych obliczen, ale to temat na inne opowiadanie (i jestto mocno zalezne od sprzetu).
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Kod 64-bitowy
Zalety:
Wiecej rejestrów: nie trzeba trzymac zmiennych i wynikówposrednich w pamiecu RAM.Efektywne wywołania procedur: przekazywanieparametrów w rejestrach.64-bitowe rejestry do liczb całkowitych.Lepsza gospodarka przydziałem duzych bloków pamieci.Wbudowany repertuar SSE2.Wzgledna adresacja danych, wydajny kod relokowalny.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Kod 64-bitowy
Wady:
Dwa razy wieksze adresy i pozycje stosu: kłopoty zpamiecia buforowa.Dostep do statycznych i globalnych tablic wymaga wiecejinstrukcji dla duzych obrazów pamieci. Dotyczy głównieWindows i Maca.Bardziej skomplikowane obliczanie adresu gdy rozmiarwiekszy niz 2GB.Niektóre instrukcje dłuzsze.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Funkcje intrinsic w C++
Nowe podejscie w łaczeniu kodu z róznych poziomów.Funkcje intrinsic to znane kompilatorowiwysokopoziomowe reprezentacje instrukcji maszynowych.Przykład: dodawanie wektorów zmiennopozycyjnychADDPS w C++ mozna zapisac funkcja _mm_add_ps.Ponadto mozna zdefiniowac odpowiednia klase wektorów iprzeciazyc dla niej operator +.Funkcje intrinsic wystepuja w kompilatorach Microsoft,Intela i GNU.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Ogladanie kodu z kompilatora
Rózne powody:
Sprawdzanie, czy nie widac wyraznych miejsc do recznegoprzepisania w asemblerze (lub przestawienia flagkompilatora, np. -O3 ;-)Potraktowanie kompilatora jako inteligentnej maszynistki, akodu jako wygodniejszej bazy niz pisanie od zera.Ten kod co najmniej ma dobrze zrobione interfejsy zotoczeniem, a tam czesto najwiecej kłopotów.
A czasem wykryjemy bład w kompilatorze
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Kompilator Intel C++ (parallel composer)
Intrinsics dla wektorów, automatyczna wektoryzacja.OpenMP i automatyczne zrównoleglanie watków.CPU dispatch: wersje dla róznych procesorów.Najlepiej zoptymalizowane biblioteki matematyczne (chocczasem nie umiały podzielic).Wada: kod moze wolniej działac na procesorach AMD iVIA, nalezy wtedy pomijac dispatch.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Kompilator GNU
Intrinsics dla wektorów, automatyczna wektoryzacja.OpenMP i automatyczne zrównoleglanie watków.Optymalizacja bibliotek czeka na swoja kolej.Ale akceptuje matematyczne biblioteki wektorowe AMD iIntela.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Ograniczenia sprzetowe
Na ARM rejestry sa 32-bitowe.Nalezy unikac typów char i short dla liczników petli, botrzeba w kodzie recznie badac zakresy, np. dla instrukcji
short i;...i++;
kompilator za kazdym razem musi badac, czy nie nastapiłoprzekroczenie zakresu i „przerzucac” na zero. Rejestry sabowiem 32-bitowe, wiec brak sygnalizacjiprzepełnienia/przeniesienia dla 16 bitów.Tu takze kompilator jest bezbronny.Oczywiscie w procesorze x86 nie ma tych problemów.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Instrukcje zalezne
Czas wykonania ciagu instrukcji zaleznych (te sameargumenty i/lub wyniki) równy jest sumie ich latency —wymaganej liczby cykliJesli instrukcje sa niezalezne, to kolejna instrukcjazaczyna sie wczesniej i ten czas znaczaco maleje, np. kod
double list[100], sum = 0.;for (int i = 0; i < 100; i++)
sum += list[i];
warto zastapic przez
double list[100], sum1 = 0., sum2 = 0., sum3 = 0., sum4 = 0.;for (int i = 0; i < 100; i += 4) {
sum1 += list[i];sum2 += list[i+1];sum3 += list[i+2];sum4 += list[i+3];
}sum1 = (sum1 + sum2) + (sum3 + sum4);
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Zaleznosci
Czasem wyglada to dziwnie, na przykład instrukcjeprzypisania
y = a + b + c + d;
warto zastapic przez
y = (a + b) + (c + d);
Specyfikacja wielu jezyków programowania nakładawymóg wykonywania od lewej do prawej (np. zeby błedyzaokraglen były zawsze takie same) i kompilator nic wtedynie moze zrobic.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Rejestry czesciowe
Niektóre CPU robia out of order execution ale nie sa wstanie przemianowac rejestrów czesciowych (ax, ah, al).Powoduje to opóznienie w ponizszym kodzie, poniewaztrzecia instrukcja musi czekac na górne 16 bitów zmnozenia
imul eax,6mov [mem2],eaxmov ax,[mem3] ;operandy 16-bitoweadd ax,2mov [mem4],ax
Jesli zastapimy te instrukcje przez
movzx eax,[mem3]
to zaleznosc zostaje zlikwidowana.Pewnie dlatego w trybie 64-bitowym dzieje sie toautomatycznie przy przesłaniach 32-bitowych.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Zmiany kolejnosci
Głównie na mocno potokowanych RISCach (np. ARM),wymuszone specyfika procesora
Na ARM9TDMI dla instrukcji ładowania z pamieci (np.LDR) nie nalezy przez dwa cykle uzywac załadowanejwartosci.Mnozenie trwa tyle samo co mnozenie z akumulacja(MLA). Wniosek oczywisty.Na ARM10E instrukcje wielokrotnego ładowania z pamiecii zapisywania do niej działaja „w tle”. Pozornie wieczajmuja jeden cykl, o ile nie próbujemy uzywac tychrejestrów w kolejnej instrukcji.Na Intel XScale instrukcja LDRD ładuje dwa słowa naraz wjednym cyklu. Ale nie nalezy uzywac pierwszego rejestruprzez dwa kolejne cykle, a drugiego przez trzy.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Skoki i procedury
Pobieranie kodu po (nieoczekiwanym) skoku generujeopóznienia rzedu 1-3 cykli.Najwieksze, gdy adres docelowy wypada pod koniec16-bajtowego bloku (ramka). Paradoks: warto czasemwczesniej w kodzie zastapic krótsza postac instrukcjidłuzsza, aby osiagnac wyrównanie.Do przewidywania powrotów z procedur (ret) słuzy tzw.return stack buffer, zwykle o rozmiarze do 16 elementów.Nie nalezy ogłupiac mechanizmu wyskakujac z procedurczy tez potajemnie zdejmujac adresy powrotne ze stosu(albo uzywac ret jako skoku posredniego).Wywołania redukcyjne (tail calls) robi sie przez skoki!
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja
Metaprogramowanie
Zamiast pisac pokretne makra asemblera albo naduzywac m4lepiej pisac programy, które generuja inne programy lub ichczesci:
Generatory tablic sinusów, cosinusów albo latprzestepnychPrzetwarzajace plik binarny na postac zródłowaZamieniajace bitmapy na procedury szybkiegowyswietlaniaWydobywajace rózne aspekty z tego samego koduSpecjalizowany kod w asemblerze na podstawie skryptu wScheme lub innym jezyku i dodatkowych ograniczen.
Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja