Assembly HOWTO: CZY POTRZEBUJESZ ASEMBLACJI?
Następna strona
Poprzednia strona
Spis treści
2. CZY POTRZEBUJESZ ASEMBLACJI?
No, nie chciałbym przeszkadzać w tym co robisz,
ale tu jest kilka porad ciężko zarobionego doświadczenia.
2.1 Za i Przeciw
Korzyści Assemblacji
Assemblacja może wyrazić mocno niskopoziomowe rzeczy:
masz dostęp do zależnych-od-maszyny rejestrów i I/O.
możesz kontrolować dokładnie zachowanie kodu
w krytycznych sekcjach które mogą wywołać martwe punkty
pomiędzy wieloma wątkami programowymi lub urządzeniami.
możesz złamać ustalenia zwykłego kompilatora,
który może pozwalać na pewne optymalizacje
(jak chwilowe łamanie zasad o przydzielaniu pamięci,
wątkach, konwencji wywołań, itd).
możesz budować interfejsy pomiędzy fragmentami kodu
używającego pewnych niekompatybilnych konwencji
(np. produkowanego przez różne kompilatory,
lub oddzielonego nisko-poziomowym interfejsem).
masz dostęp do nieużywanych trybów twojego procesora
(np. 16 bitowy tryb interfejsu startowego, instrukcji chip-owych
(przyp.tłum.) lub dziedziczonego kodu na Intel PC)
możesz produkować rozsądnie szybki kod dla zwartych pętli
by poradzić sobie ze złym nie-zoptymalizowanym kompilatorem
(po co, są przecież dostępne wolne zoptymalizowane kompilatory!)
możesz produkować kod gdzie
ale tylko na procesorach ze znanym czasem instrukcji,
który ogólnie wyłącza cały przepływ ....
możesz produkować ręcznie-zoptymalizowany kod
który jest perfekcyjnie dostosowany do twojej szczególnej konfiguracji sprzętowej,
a zatem do nikogo więcej.
możesz pisać pewien kod dla twojego kompilatora nowego języka
(to jest coś którzy mogą zrobić nieliczni, i także oni, nie często).
Niekorzyści Assemblacji
Assemblacja jest bardzo nisko-poziomowym językiem
(najniższym jest ręczne-kodowanie w kodach binarnych instrukcji).
To znaczy
jest długo i monotonnie pisać wszystko od początku,
jest mocno podatna na błędy,
błędy będą bardzo trudne do wyśledzenia,
jest to bardzo trudno zrozumieć i modyfikować,
np. utrzymywać
rezultat jest bardzo nie-przenośny na inne architektury,
aktualne i przyszłe,
twój kod będzie zoptymalizowany tylko w pewnych implementacjach
na tej samej architekturze:
dla przykładu, pośród platform Intelowskich,
każdy wariant CPU i jego odmian
(względy ukryte, przepustowość, pojemność
jednostek obliczeniowych, cache'y, RAM-u, szyny, dysków,
obecności FPU, rozszerzeń MMX, itd)
daje potencjalnie całkowicie różne techniki optymalizacji.
Warianty CPU już włączone
Intel 386, 486, Pentium, PPro, Pentium II;
Cyrix 5x86, 6x86; AMD K5, K6.
Nowe warianty pojawiają się więc nie spodziewaj się, że każdy listing
twojego kodu będzie na czasie.
twój kod może być także nieprzenośny przez różne
platformy systemowe na tej samej architekturze, przez brak właściwych narzędzi
(no, GAS wydaje się pracować na wszystkich platformach;
NASM wydaje się pracować lub być w stanie pracować na platformach intelowskich).
spędzisz więcej czasu na kilku detalach,
i nie będziesz mógł skoncentrować się na małych i dużych algorytmach,
które są w stanie przynieść największą część przyspieszenia.
[np. możesz spędzić trochę czasu budując bardzo szybkie
prymitywy manipulacji na listach/tablicach w assemblerze;
tylko hash-tablice (przyp.tłum.) mogą mocno przyspieszyć twój program;
lub i innym przypadku, drzewka binarne;
lub pewne wysokopoziomowe struktury rozprowadzane przez klaster
CPU]
mała zmiana w konstrukcji algorytmu mogłaby całkowicie
unieważnić twój cały istniejący assemblowany kod.
Tak więc także ty masz być gotowy (i w stanie) by przepisać to wszystko,
lub będziesz przywiązany do poszczególnych rozwiązań algorytmicznych;
W kodzie który nie wypada daleko od standardowych testów,
komercyjne zoptymalizowane kompilatory wykonują prawe ręcznie-kodowany assembler
(no, to jest mniejszą prawdą na architekturze x86
niż na architekturach RISC-owych;
i być może mniejszą prawdą niż dla szeroko dostępnych/wolnych kompilatorach;
jakkolwiek, dla typowego kodu w C, GCC jest całkiem dobry);
I w dowolnym przypadku, jak powiedział wstrzemięźliwy John Levine na comp.compilers,
``kompilatory mocno ułatwiają używanie złożonych struktur danych,
i kompilatory nie dają znudzenia w połowie drogi
i generują całkiem dobry kod.''
One także będą prawidłowo przechodzić transformacje kodu
poprzez cały (olbrzymi) program
podczas optymalizacji kodu pomiędzy procedurami i granicami modułów.
Ocenianie
Podsumowując, chociaż możesz uznać
że użycie assemblacji jest czasami konieczne,
a nawet pożyteczne w kilku przypadkach gdzie nie jest konieczne,
będziesz chciał:
minimalizować użycie kodu assemblera,
hermetyzować ten kod w dobrze zdefiniowanych interfejsach
mieć kod assemblera generowany automatycznie
ze wzorców wyrażonych w wysokopoziomowym języku
niż w assemblerze (np. assemblerowe makra GCC inline)
mieć narzędzia automatyzujące tłumaczenie tych programów
w kod assemblera
mieć ten kod zoptymalizowany jeśli jest to możliwe
Nade wszystko,
np. pisać (rozszerzać do) zoptymalizowanych wnętrzności kompilatora.
Nawet w przypadkach kiedy Assemblacja jest konieczna (np. rozwój OS)
możesz uznać, że nie aż do tego stopnia
i trzymać się w/w zasad.
Obejrzyj źrodła jądra Linux-a zwracając uwagę:
jak mało assemblacji jest konieczne,
by uzyskać szybki, niezawodny, przenośny, utrzymywalny OS.
Także udana gra taka jak DOOM została prawie całkowicie napisana w C,
z mała częścią napisaną w assemblerze tylko do przyśpieszenia jej działania.
2.2 Jak NIE używać Assemblera
Ogólne zasady uzyskania efektywnego kodu
Jak rzekł Charles Fiterman na comp.compilers
o 'człowieku kontra kod assemblera wygenerowany przez komputer',
``Człowiek powinien zawsze wygrać i oto przyczyna.
Po pierwsze człowiek pisze całość w języku wysokiego poziomu.
Po drugie zarysowuje gdzie mogą wystąpić gorące punkty gdzie program spędza swój czas.
Po trzecie ma kompilator produkujący kod assemblera dla tych małych
sekcji kodu.
Po czwarte ręcznie dostosowuje je by znaleźć małe korzyści
nad wygenerowanym przez maszynę kodem.
Człowiek wygrywa poniewaz umie używać maszyny.''
Języki ze zoptymalizowanymi kompilatorami
Języki takie jak
ObjectiveCAML, SML, CommonLISP, Scheme, ADA, Pascal, C, C++,
wsród innych,
wszystkie mają wolne zoptymalizowane kompilatory,
które zoptymalizują masę twoich programów,
i często będą lepsze niż ręczny kod assemblera nawet dla szczelnych pętli,
umożliwiając ci skoncentrowanie się na wysokopoziomowych szczegółach,
i bez zakazywania ci złapania
kilku procent wykonania w wyżej wymieniony sposób
w momencie gdy osiągniesz stabilny rozwój.
Oczywiście, są także komercyjne zoptymalizowane kompilatory
dla większości z tych języków.
Pewne języki mają kompilatory produkujące kod w C,
który może być dalej zoptymalizowany przez dany kompilator C.
Takimi są LIST, Scheme, Perl i wiele innych.
Prędkość jest całkiem dobra.
Ogólne zasady przyśpieszania twojego kodu
W celu przyspieszenia kodu
powinieneś robić zrobić to tylko dla fragmentów programu
które narzędzie profilujące konsekwentnie określa
jako wąskie gardło wykonania.
Stąd, jeśli określisz fragmenty kodu jako zbyt wolne, powinieneś
najpierw spróbować lepszego algorytmu;
następnie spróbować skompilować go zamiast interpretować;
następnie spróbować włączyć optymalizacje twojego kompilatora;
następnie dać kompilatorowi wskazówki jak optymalizować
(wypisywanie informacji w LISP-ie; używanie rejestrów w GCC;
i pełno innych opcji w większości kompilatorów, itd).
następnie można cofnąć się do programowania w assemblerze
Na końcu, przed zejściem do pisania w assemblerze,
powinieneś prześledzić wygenerowany kod,
sprawdzając czy problem nie leży faktycznie w złej generacji kodu,
co jest możliwe ale nie w wypadkach:
kod wygenerowany przez kompilator może być lepszy niż mógłbyć napisać,
w szczególności na nowoczesnych architekturach!
Wolne części programu mogą być równie zagmatwane.
Największym problemem nowoczesnych architektur z szybkimi procesorami
są pewne opóźnienia dostępu do pamięci, nietrafiony dostęp do cache,
i TLB oraz błędy stronnicowania;
optymalizacja z użyciem rejestrów staje się wtedy mniej użyteczna,
i zyskałbyć więcej po przemyśleniu struktury danych oraz wykorzystując
wątkowanie gdyż uzyskałbyś lepsze umiejscowienie w dostępie do pamięci.
Być może poźniej dopiero całkowicie odmienne spojrzenie na problem, pomoże go rozwiązać.
Sprawdzanie kodu generowanego przez kompilator
Jest wiele powodów do sprawdzenia kodu generowanego przez kompilator.
Tu jest zawarte co robić z takim kodem:
sprawdzić czy generowany kod
może być wzmocniony ręcznym kodem assemblera
(lub przełączaniem przełączników kompilatora)
gdy zachodzi taka możliwość
rozpocząć z tak wygenerowanego kodu i zmodyfikować go;
zamiast rozpoczynać wszystko od początku
bardziej ogólnie, używać generowanego kodu jako kawałków do modyfikacji,
który co najmniej daje właściwą drogę
twoim funkcjom w assemblerze interfejs do świata zewnętrznego
wyłapać błędy w kompilatorze (oby jak najrzadziej)
Standardową metodą uzyskania kodu assemblera
jest wywołanie twojego kompilatora z flagą -S.
Działa to dla większości Unix-owych kompilatorów,
włączając w to GNU C Compiler (GCC), ale YMMV.
Dla GCC, bardziej zrozumiały kod assemblera będzie wyprodukowany
po użyciu opcji -fverbose-asm.
Oczywiście, jeśli chcesz dostać dobry kod assemblera,
nie zapomnij użyć opcji optymalizacji i wskazówek!
Następna strona
Poprzednia strona
Spis treści
Wyszukiwarka
Podobne podstrony:
Assembly HOWTO pl 5 (2)Assembly HOWTO pl 4 (2)assembly howto plAssembly HOWTO pl 7 (2)Assembly HOWTO pl (2)Assembly HOWTO pl 6 (2)assembly howto pl 3Assembly HOWTO pl 1 (2)bootdisk howto pl 8PPP HOWTO pl 6 (2)NIS HOWTO pl 1 (2)cdrom howto pl 1jtz howto pl 5Keystroke HOWTO pl (2)PostgreSQL HOWTO pl 14printing howto pl 5debian apt howto plKernel HOWTO pl 12 (2)XFree86 HOWTO pl (3)więcej podobnych podstron