32
Programowanie w asemblerze Optymalizacja Zbigniew Jurkiewicz, Instytut Informatyki UW 17 stycznia 2017 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja

Programowanie w asemblerze Optymalizacjastudents.mimuw.edu.pl/~zbyszek/asm/pl/slides/opty.pdf · Przesłania warunkowe Czasem dokonujemy porównania i zaleznie od wyniku˙ chcemy

  • Upload
    lykhanh

  • View
    213

  • 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

Tuning: narzedzia

AMD Code AnalystIntel VTuneNew-Jersey Machine-Code Toolkit (w ML)http://www.eecs.harvard.edu/ nr/toolkit/

Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Optymalizacja