Materialy pom wyklad 2


Komputery CISC i RISC
f& przez wiele lat wzrost wydajności procesorów starano się uzyskać poprzez zwiększanie
wielkości i złożoności list rozkazów, jak również poprzez instalowanie bloków
funkcjonalnych, wspomagających procesor (np. pamięć podręczna);
f& badania różnych kompilatorów języków programowania pokazały jednak, że tylko
niewielki podzbiór rozkazów procesora jest używany przez kompilatory; przykładowo,
kompilatory języka C firmy Sun i GNU nie używały 71% instrukcji procesora Motorola
68020; dalsze badania innych procesorów pokazały, że najczęściej używane są rozkazy
przesyłania danych (46%), skoki (w tym wywołania i powroty z podprogramu) (27%),
rozkazy arytmetyczne (14%), porównania (10%) i rozkazy logiczne (2%);
f& pozornie, jeśli lista rozkazów procesora zawiera rozkazy zawierające złożone operacje, to
stanowi dużą wygodę dla autorów kompilatora, a wygenerowany kod jest krótszy; jednak
doświadczenia praktyczne pokazują, że złożone rozkazy maszynowe są często trudne do
wykorzystania, ponieważ kompilator musi zidentyfikować te fragmenty kodu, które
pasują dokładnie do czynności rozkazu  powoduje znaczny wzrost złożoności
kompilatora (zwłaszcza procedur optymalizacyjnych); ogólnie: kompilatory wykazują
tendencję do faworyzowania prostych rozkazów;
f& również doświadczenia praktyczne nie potwierdziły przypuszczenia, że programy
wykorzystujące złożone instrukcje będą krótsze  zwykle zawierają mniej instrukcji, ale
złożone instrukcje kodowane są za pomocą większej liczby bajtów, tak że rozmiary
programu nie ulegajÄ… istotnemu zmniejszeniu;
f& w powstałej sytuacji zaproponowano ograniczenie listy rozkazów, uproszczenie
kodowania, co pozwoliłoby na szybsze ich wykonywanie  w rezultacie podjętych prac
ukształtowała się architektura procesorów o zredukowanych listach rozkazów, znanych
jako RISC (ang. Reduced Instruction Set Computer); jednocześnie, istniejące procesory, o
rozbudowanych listach rozkazów zaliczono do typu CISC (ang. Complex Instruction Set
Computer);
f& jako charakterystyczne cechy architektury CISC wymienia siÄ™ zazwyczaj:
" lista rozkazów zawiera 100 ÷ 250 pozycji, wÅ›ród których wystÄ™pujÄ… rozkazy
realizujące złożone funkcje;
" dostÄ™pna jest duża liczba trybów adresowania 5 ÷ 20;
" czasy wykonania poszczególnych rozkazów, w zależności od stopnia
skomplikowania, zmieniają się w dość szerokich granicach;
" realizacja rozkazów oparta jest na technice mikroprogramowania;
f& z kolei dla procesorów RISC charakterystyczne są poniższe cechy:
" stosunkowo niewiele trybów adresowania;
" formaty rozkazów stałej długości, łatwe do zdekodowania;
" dostęp do pamięci operacyjnej umożliwiają tylko dwa rozkazy: load, store;
" stosunkowo obszerny zbiór rejestrów ogólnego przeznaczenia;
" rozkazy wykonują działania na argumentach zapisanych w rejestrach (a nie w
pamięci operacyjnej);
" sterowanie wykonywaniem rozkazów realizowane jest układowo (nie
mikroprogramowo);
" intensywne wykorzystanie przetwarzania potokowego (występuje też w innych,
nowoczesnych procesorach); także kompilatory generują kod uwzględniający
wymagania przetwarzania potokowego.
Rejestry ogólnego przeznaczenia w procesorach RISC
f& współczesne procesory są w stanie przetwarzać dane z szybkością większą niż szybkość
pamięci głównej (operacyjnej), co może powodować przestoje procesora; znaczne
usprawnienie komunikacji procesora i pamięci głównej przyniosło wprowadzenie pamięci
podręcznych, ale nadal komunikacja ta stanowi wąskie gardło;
f& procesory RISC mają zazwyczaj duże zestawy rejestrów ogólnego przeznaczenia
(nazywanych też rejestrami uniwersalnymi) i do zadań obliczeniowych używają tylko
rejestrów, a nie rejestrów i pamięci; w rezultacie potrzeba mniej dostępów do pamięci i
problem komunikacji procesora z pamięcią staje się mniej krytyczny;
f& zazwyczaj długość słowa rozkazu jest równa szerokości magistrali danych, więc każdy
rozkaz może być pobrany w jednym cyklu pamięci (w procesorach CISC rozkazy często
muszą być pobierane w czasie kilku cykli pamięci);
f& w niektórych procesorach RISC używane są oddzielne pamięci dla rozkazów i danych
(tzw. architektura harwardzka), z których każda obsługiwana jest przez oddzielne
magistrale adresowe i danych.
Przekazywanie parametrów do podprogramów w procesorach RISC
f& w programach kodowanych w językach wysokiego poziomu procedury i funkcje
wywołują często inne procedury i funkcje, a także same są wywoływane; w dalszej
używać będziemy terminu procedura także w znaczeniu funkcji;
f& w procesorach CISC parametry wywołań przekazywane są zazwyczaj przez stos, co
wymaga wielu odwołań do pamięci; z kolei w procesorach RISC duża liczba rejestrów
ogólnego przeznaczenia umożliwia przekazywanie parametrów przez rejestry, co znacznie
zmniejsza liczbę odwołań do pamięci;
f& procedura wykonuje działania:
" na parametrach przekazanych jej podczas wywoływania;
" na zmiennych lokalnych, w których zapisywane są wyniki pośrednie;
" na zmiennych globalnych, dostępnych także dla innych funkcji
 w procesorach RISC wszystkie te zmienne i parametry przechowywane sÄ… w
rejestrach, zorganizowanych w postaci tzw. okien; w typowych rozwiÄ…zaniach okno
zawiera 32 rejestry, a liczba okien wynosi np. 16;
f& każda procedura korzysta z własnego okna rejestrów, przy czym okna sąsiednich procedur
(w sensie wywoływania) częściowo się nakładają, co ułatwia przekazywanie parametrów;
f& wywołanie procedury powoduje automatyczne przełączenie procesora do użycia innego
okna rejestrów; w dowolnej chwili widzialne i adresowalne jest tylko jedno okno
rejestrów, tak jak gdyby istniał tylko jeden zestaw rejestrów;
f& najpierw rozpatrzymy wersję uproszczoną bez rejestrów przeznaczonych dla zmiennych
globalnych; okno podzielone jest na trzy obszary o stałych rozmiarach obejmujące:
" rejestry parametrów (zawierają parametry przekazane przez procedurę
wywołującą, oraz wyniki, które mają być przekazane z powrotem);
" rejestry lokalne (używane do przechowywania zmiennych lokalnych procedury);
" rejestry tymczasowe (używane do wymiany parametrów i wyników z następnym
niższym poziomem, innymi słowy z procedurą wywołaną przez procedurę
bieżącą);
Rejestry Rejestry Rejestry Poziom
parametrów lokalne tymczasowe zagnieżdżenia J
Wywołanie/powrót
Poziom Rejestry Rejestry Rejestry
zagnieżdżenia J + 1 parametrów lokalne tymczasowe
f& rejestry tymczasowe sÄ… jednego poziomu sÄ… fizycznie tymi samymi rejestrami, co rejestry
parametrów następnego niższego poziomu; zatem procedura przygotowuje najpierw
parametry wywołania w rejestrach tymczasowych, a następnie wywołuje żądaną
procedurę  po wywołaniu procedury następuje automatyczne przełączenie procesora do
następnego okna, a rejestry tymczasowe w aktualnym oknie są widoczne jako rejestry
parametrów;
f& w procesorach RISC liczba rejestrów ogólnego przeznaczenia (tworzących okna) jest dość
duża, ale nie wystarczająca przy dużych głębokościach zagnieżdżenia; w takich
przypadku argumenty starszych procedur muszą być zapisywane w pamięci, i odtwarzane
pózniej, gdy maleje głębokość zagnieżdżenia;
f& niektóre zmienne w programie mają charakter globalny i powinny być dostępne dla
wszystkich procedur; zazwyczaj zmienne takie lokowane są w pamięci operacyjnej, co
może być nieefektywne przy dużej liczbie odwołań; w procesorach RISC wydziela się
pewną liczbę rejestrów do przechowywania zmiennych globalnych  są one dostępne w
każdym oknie; jednak nierzadko liczba zmiennych globalnych jest większa od liczby
rejestrów i kompilator musi wybrać zmienne globalne najczęściej używane; ten sam
problem występuje także, gdy liczba zmiennych lokalnych procedury przekracza liczbę
rejestrów (np. 8)  w rozwiązaniach tego problemu korzysta się z algorytmów
kolorowania grafów;
f& stosowany jest zunifikowany schemat numerowania rejestrów:
Nr rejestru Przeznaczenie
zmienne globalne
0 ÷ 7
rejestry tymczasowe
8 ÷ 15
zmienne lokalne
16 ÷ 23
parametry
24 ÷ 31
w rezultacie każda procedura ma dostęp do pojedynczego okna, zawierającego
identycznie oznaczone rejestry;
f& wprowadzenie dużej liczby rejestrów w procesorach RISC może być traktowane jako
rozwiązanie podobne do pamięci podręcznej  każde z nich ma pewne wady i zalety; w
programach o niewielkim stopniu zagnieżdżenia procedur, rejestry procesora pozostają
częściowo niewykorzystane; z kolei pamięć podręczna może dynamicznie reagować na
rozwój sytuacji, ale dane do pamięci podręcznej wczytywane są blokami, które tylko
częściowo są wykorzystywane; uważa się jednak, że adresowanie rejestrów jest dość
proste, w porównaniu do bardziej złożonego mechanizmu adresowania pamięci, wskutek
czego stosowanie dużej liczby rejestrów jest bardziej efektywne.
Sterowanie mikroprogramowe i układowe
f& w ujęciu skrótowym,
wykonywanie rozkazu przez
procesor rozpoczyna siÄ™
Rejestry Jednostka
pobrania rozkazu z pamięci,
procesora sterujÄ…ca
po czym identyfikowany jest
jego kod  na tej podstawie
jednostka sterujÄ…ca w
procesorze wydaje odpo-
Jednostka
wiednie dyspozycje dla
jednostki arytmetyczno-
arytm.  logiczna
logicznej, rejestrów i innych
podzespołów procesora,
kierując odpowiednio przepływem i przetwarzaniem danych, zgodnie rozpoznanym
kodem rozkazu;
f& istniejÄ… dwa podstawowe sposoby konstrukcji jednostki sterujÄ…cej procesora: jednostka
sterująca mikroprogramowalna i jednostka sterująca układowa;
f& koncepcję sterowania mikroprogramowanego można określić jako sterowanie za pomocą
wewnętrznego procesora, wbudowanego w główny procesor; wewnętrzny mikroprocesor
zawiera własny wskaznik instrukcji (licznik rozkazów) i wykonuje mikroprogram
zapisany w pamięci ROM (lub w tablicy logicznej PLA  ang. programmed logic array);
f& mikroprogram składa się z szeregu mikrorozkazów, a każdy mikrorozkaz zawiera
sekwencję bitów, która reprezentuje mikrooperację sterującą przemieszczaniem
informacji między różnymi podzespołami i rejestrami procesora; wśród mikrorozkazów
istnieją także skoki warunkowe i bezwarunkowe, zmieniające kolejność, w jakiej
wykonywane sÄ… mikrorozkazy;
f& w tym kontekście rozkazy zwykłego programu wykonywanego przez procesor nazywane
są makrorozkazami; termin makrorozkazy używany jest także w innym znaczeniu w
językach programowania i opisuje instrukcje zastępowane w treści programu przez teksty
makrodefinicji;
f& ponieważ wiele mikroprogramów wymaga jednakowych sekwencji mikrorozkazów,
używane są także mikroprocedury;
f& sterowanie mikroprogramowe umożliwia stosunkowe łatwe tworzenie nowych wersji
procesorów o bardziej rozbudowanej liście rozkazów; ze względu na postępy technologii
elektronicznej rozmiary kolejnych wersji procesorów pozostają często niezmienione;
ponadto konstrukcje ze sterowaniem mikroprogramowym pozwalają na względnie łatwe
usuwanie błędów projektowych na etapie prototypowym;
f& sterowanie układowe stanowi złożony układ cyfrowy zawierający bramki, przerzutniki i
inne podzespoły; istotnym elementem takiego sterowania jest licznik sekwencji, który jest
zwiększany o 1 w kolejnych fazach wykonania rozkazu; po załadowaniu kolejnego
rozkazu do rejestru rozkazów bity kodu operacji wraz licznikiem sekwencji podawane są
na wejście układu logicznego, który generuje odpowiednie sygnały sterujące;
f& sterowanie układowe pozwala zazwyczaj na nieco szybsze wykonywanie rozkazów; na
poziomie projektowania, sterowanie układowe jest mniej elastyczne niż
mikroprogramowe i projekty nie mogÄ… Å‚atwo modyfikowane;
f& sterowanie układowe nie może być stosowane ze złożonymi formatami rozkazów.
Przetwarzanie potokowe
f& przetwarzanie potokowe stanowi pewną technikę wykonywania rozkazów etapami, co
umożliwia przyspieszenie pracy procesora; przetwarzanie potokowe rozkazów jest
podobne do użycia linii montażowej w zakładzie produkcyjnym  możliwa jest
jednoczesna praca nad wyrobami w różnych stadiach produkcji; w potoku na jednym
końcu przyjmowane są nowe elementy wejściowe, zanim jeszcze elementy poprzednio
przyjęte ukażą się na wyjściu;
f& przetwarzanie potokowe jest stosowane w różnych typach procesorów, zarówno o
architekturze CISC (Motorola 68K, Pentium), jak i w procesorach o zredukowanej liczbie
rozkazów (RISC); jednak uzyskanie przyspieszenia wymaga przygotowania
odpowiedniego kodu przez kompilatory, które w przypadku architektury CISC zazwyczaj
nie uwzględniają specyfiki przetwarzania potokowego;
f& można przyjąć, że każdy etap zajmuje jeden cykl zegarowy; procesor przyjmuje nowy
rozkaz do potoku po każdym cyklu zegara, po czym rozkaz przechodzi kolejno przez
poszczególne etapy; taka realizacja nie skraca czasu wykonywania rozkazu, ale zwiększa
całkowitą przepustowość, powodując zakończenie jednego rozkazu po każdym cyklu
zegara;
f& liczba etapów realizacji rozkazu zależy od konstrukcji procesora, ale podstawowe
znaczenie majÄ… cztery etapy:
" pobranie rozkazu (ang. instruction fetch), stanowi etap, w którym rozkaz
pobierany jest z pamięci głównej albo z pamięci podręcznej;
" dekodowanie rozkazu (ang. instruction decode)  rozkaz jest dekodowany, przy
czym identyfikuje się argumenty zródłowe, które przepisywane są do rejestrów
pomocniczych wejściowych procesora (niedostępnych na poziomie
programowania);
" wykonanie rozkazu (ang. execution)  procesor wykonuje operacje na
argumentach zapisanych w rejestrach pomocniczych, a uzyskane wyniki zapisuje
do rejestrów pomocniczych wyjściowych;
" zapisanie wyników (ang. write-back) stanowi etap, w którym zawartości
rejestrów pomocniczych wyjściowych zostają skopiowane do zwykłych rejestrów
procesora lub do lokacji pamięci;
Cykl Etapy
Pobranie Dekodo-wanie Wykonanie Zapisanie
rozkazu rozkazu rozkazu wyników
rozkazu
1 Rozkaz 1
2 Rozkaz 2 Rozkaz 1
3 Rozkaz 3 Rozkaz 2 Rozkaz 1
4 Rozkaz 4 Rozkaz 3 Rozkaz 2 Rozkaz 1
5 Rozkaz 5 Rozkaz 4 Rozkaz 3 Rozkaz 2
f& w procesorach klasy CISC dekodowanie rozkazu jest bardziej złożone i z tego względu
przedstawiane jest w trzech etapach:
" właściwe dekodowanie rozkazu  obejmuje określenie kodu operacji i
specyfikatorów argumentów;
" obliczanie argumentów  obejmuje obliczanie adresu efektywnego każdego
argumentu zródłowego (z ewentualnym wykorzystaniem rejestrów modyfikacji
adresowych, adresowania pośredniego itp.);
" pobieranie argumentów  pobranie argumentów z pamięci lub z rejestrów i
przepisanie do rejestrów pomocniczych wejściowych;
f& po zapełnieniu potoku, po każdym cyklu zegarowym zostaje zakończony jeden rozkaz 
dla podanego przykładu współczynnik przyspieszenia wynosi 4; warto dodać,
poszczególne rozkazy wykonywane są zazwyczaj w ciągu kilku cykli zegarowych,
natomiast producenci procesorów podają liczbę cykli potrzebnych dla wykonania
poszczególnych rozkazów przy założeniu, że rozkaz stanowi jeden z wielu kolejno
wykonywanych rozkazów; zatem wartości tej nie należy traktować jako czasu
wykonywania rozkazu, ale raczej jako miarę wydajności procesora (gotowe samochody w
fabryce pojawiają się co dwie minuty, ale montaż pojedynczego samochodu trwa wiele
godzin);
f& istnieją różne przyczyny, które powodują zmniejszenie współczynnika przyspieszenia:
" realizacja niektórych etapów może powodować konflikty dostępu do pamięci;
" jeśli czasy trwania poszczególnych etapów mogą być niejednakowe, to na
różnych etapach wystąpi pewne oczekiwanie;
" w programie występują skoki (rozgałęzienia) warunkowe, które mogą zmienić
kolejność wykonywania instrukcji, a tym samym unieważnić kilka pobranych
rozkazów  muszą one być usunięte z potoku, a potok musi być zapełniony
nowym strumieniem rozkazów;
" wystąpienie przerwania sprzętowego lub wyjątku procesora stanowi zdarzenie
nieprzewidywalne i również pogarsza przetwarzanie potokowe;
" niektóre rozkazy wymagają dodatkowych cykli (np. do ładowania danych);
" czasami rozkazy muszą oczekiwać z powodu zależności od nie zakończonych
poprzednich rozkazów  system musi zawierać rozwiązania zapobiegające tego
rodzaju konfliktom;
f& różne zródła zakłóceń przetwarzania potokowego nazywane są hazardami;
f& w trakcie wykonywania poszczególnych instrukcji używane są rozmaite bloki
funkcjonalne i podzespoły komputera, jak pamięć operacyjna i podręczna, magistrale,
zbiory rejestrów, jednostka arytmetyczno-logiczna procesora i inne  stanowią one
zasoby komputera, które muszą być odpowiednio udostępniane (dzielone) między
rozkazami znajdujÄ…cymi siÄ™ w potoku procesora;
f& jeśli dwa etapy potrzebują dostępu do tego samego zasobu w tym samym czasie, to potok
musi zostać zamrożony do czasu rozwiązania konfliktu, co prowadzi do zwiększenia
liczby cykli potrzebnych do realizacji rozkazu;
f& problemy można rozwiązać poprzez zainstalowanie dwóch lub więcej modułów
sprzętowych tego samego typu, np. używane są podwójne bufory do pobierania rozkazów,
stosuje się także oddzielne pamięci podręczne dla rozkazów i danych;
f& inny problem stanowią zależności danych:
1. odczyt po zapisie  operacja odczytu, np. rejestru przez rozkaz, który następuje
po rozkazie dokonującym zapisu, może być przeprowadzona dopiero po
zakończeniu zapisu;
2. zapis po odczycie  operacja zapisu rejestru może być wykonana dopiero
wówczas, gdy poprzedni rozkaz, odnoszący się do tego rejestru, wykonał
odczyt;
3. zapis po zapisie  rozkaz musi oczekiwać na zapisanie rejestru, jeśli poprzedni
rozkaz nie zrealizował zapisu;
f& na ogół zależności danych mogą być wyeliminowane w sposób programowy poprzez
zmianę kolejności rozkazów (ang. static scheduling  szeregowanie statyczne), co można
uzyskać poprzez odpowiednią kompilację  pozwala to na wyeliminowanie około 70%
opóznień;
f& stosowane jest także szeregowanie dynamiczne, realizowane sprzętowo; w niektórych
architekturach stosowana jest technika wyprzedzania argumentów (ang. operand
forwarding), która polega na udostępnieniu dodatkowej ścieżki danych umożliwiającej
bezpośrednie przekazanie argumentu z rejestru pomocniczego wyjściowego do rejestru
pomocniczego wejściowego, co przeciwdziała zamrożeniu potoku w sytuacji "odczyt po
zapisie";
f& technika notowania (ang. scoreboarding) polega na dołączeniu do każdego rejestru
jednobitowego znacznika: stan 1 tego znacznika wskazuje, że rejestr zawiera dane
poprawne, stan 0  niepoprawne; jeśli pobierany rozkaz zapisuje wynik do rejestru, to w
trakcie dekodowania tego rozkazu znacznik zostaje ustawiony w stan 0; stan 0 sygnalizuje
innym rozkazom, że zawartość rejestru zostanie zmieniona i zmusza te rozkazy do
czekania; po wykonaniu rozkazu i uaktualnieniu zawartości rejestru znacznik ustawiany
jest w stan 1, co umożliwia wznowienie zamrożonych rozkazów; omawiana technika
stosowana jest w procesorze Motorola 88010.
Konflikty sterowania
f& w przetwarzaniu potokowym powinien być zapewniony stały dopływ rozkazów do
poczÄ…tkowego etapu potoku; zasadniczÄ… przeszkodÄ… w realizacji tego postulatu jest rozkaz
skoku (rozgałęzienia) warunkowego  aż do zakończenia wykonywania tego rozkazu nie
jest możliwe stwierdzenie czy skok zostanie wykonany, czy nie;
f& zatem rozkaz skoku warunkowego powoduje, że adres następnego rozkazu
przewidzianego do pobrania jest nieznany; w tej sytuacji realizacja etapu pobierania
mogłaby nastąpić dopiero po zakończeniu wykonywania rozkazu skoku warunkowego;
związana z tym strata czasu może być zmniejszona przez przewidywanie rozgałęzień
(zgadywanie);
f& przewidywania statyczne nie zależą od historii wykonania rozkazu skoku; zwykle
przyjmuje się jedno z niżej wymienionych założeń:
" rozgałęzienie zawsze nie nastąpi, tzn. warunek testowany przez rozkaz skoku nie
będzie spełniony i będą pobierane kolejne rozkazy za rozkazem skoku;
" rozgałęzienie zawsze nastąpi i należy pobrać kolejne rozkazy z lokacji pamięci
wskazanej przez adres rozkazu skoku (przez cel rozgałęzienia);
" dla pewnych rozkazów sterujących zakłada się, że rozgałęzienie zawsze nastąpi,
dla innych przyjmuje się, że nie nastąpi  badania eksperymentalne pokazują, że
metoda zapewnia prawdopodobieństwo sukcesu ponad 75%;
f& jeśli przewidywanie okaże się nietrafne, to koniecznie jest unieważnienie częściowo
przetworzonych rozkazów i wypełnianie potoku od nowa;
f& przewidywanie za pomocÄ… strategii dynamicznych oparte jest na historii wykonania
rozkazów skoków warunkowych (rozgałęzień warunkowych); zwykle z każdym rozkazem
zwiÄ…zane sÄ… jeden lub dwa bity odzwierciedlajÄ…ce najnowszÄ… historiÄ™ rozkazu  bity te
pozwalają procesorowi podjąć odpowiednią decyzję po ponownym natrafieniu na rozkaz;
f& za pomocÄ… pojedynczego bitu historii rejestruje siÄ™ wystÄ…pienie lub niewystÄ…pienie skoku
(rozgałęzienia) podczas ostatniego wykonania rozkazu; w pętli błędne przewidywanie
wystąpi dwukrotnie: raz przy wejściu do pętli i raz przy jej opuszczaniu;
f& lepsze przewidywanie można uzyskać poprzez zastosowanie dwóch bitów, np. do
zarejestrowania dwóch ostatnich przypadków wykonania rozkazu; jeśli dwa ostatnie skoki
zostały wykonane jednakowo, to przy następnym należy wybrać tę samą ścieżkę; jeśli
przewidywanie okazało się błędne, to pozostaje bez zmian aż do ponownego wykonania
rozkazu  dopiero dwa kolejne błędne przewidywania powodują zmianę decyzji;
f& dość skutecznym rozwiązaniem problemu predykcji skoków może być tablica historii
skoków; tablica ta rejestruje wyniki działania rozkazów skokowych w celu przewidywania
ich zachowania w przyszłości  jest to szczególnie przydatne w odniesieniu do pętli
programowych; jednak zawartość tablicy może być wykorzystana dopiero po
zdekodowaniu adresu rozkazu skokowego;
f& w procesorze Pentium używany jest bufor adresów rozgałęzień BTB (ang. branch target
buffer), w którym przechowywana jest historia 256 skoków warunkowych (rozgałęzień);
f& gdy zostanie napotkany rozkaz skoku warunkowego, określany jest jego adres docelowy
(efektywny), a następnie druga kolejka rozkazów rozpoczyna pobieranie instrukcji
kierując się przewidywaniami na podstawie BTB; jeśli przewidywanie nie sprawdziło się,
to obie kolejki są opróżniane i ponownie napełniane; w Pentium przyjęto, że jeśli pętla
wykonała skok, to w kolejnych iteracjach skok ten zostanie ponownie wykonany;
f& stosowane są również inne rozwiązania związane z przetwarzaniem rozkazów skokowych
 najważniejsze z nich omawiane są poniżej;
f& niekiedy używane są dwa bufory typu FIFO, do których pobierane są rozkazy
wykonywane w każdej z gałęzi rozwidlenia; w zależności od wyniku skoku używany jest
jeden lub drugi bufor; bufor zawierający nie używane rozkazy jest opróżniany po
napotkaniu w strumieniu rozkazów kolejnego skoku warunkowego; metoda ta przestaje
być skuteczna, gdy do bufora dostanie się dwa lub więcej rozkazów skoku warunkowego;
f& technika zwielokrotnienia strumieni (stosowana, np. w komputerze IBM 370) polega na
powieleniu początkowych części potoku i umożliwieniu równoczesnego pobrania obu
sekwencji rozkazów za pomocą dwóch strumieni; występują jednak pewne problemy:
" konflikty w zakresie dostępu do rejestrów i pamięci;
" w potoku mogą się pojawić inne skoki warunkowe, zanim zostanie przesądzone
oryginalne rozgałęzienie  w tej sytuacji potrzebny jest jeszcze jeden strumień;
f& bufor pętli jest podobny do pamięci podręcznej przeznaczonej dla przechowywania
rozkazów, ale jest znacznie mniejszy (np. 256 bajtów) i tańszy; bufor pętli zawiera n
ostatnio pobranych rozkazów, przy czym stosowane jest pobieranie z wyprzedzeniem, co
oznacza, że w stadium wykonywania znajduje się tylko początkowa część rozkazów
zawartych w buforze; w przypadku wystąpienia rozkazu skoku wewnątrz niedużej pętli
adres rozkazu wskazywać będzie na rozkaz znajdujący się buforze; podobnie w przypadku
krótkich sekwencji instrukcji if ... then ... else ... rozkazy wskazywane przez adresy
instrukcji skokowych również znajdować się będą w buforze.
Kodowanie programów bez użycia skoków
f& współczesne procesory umożliwiają równoległe wykonywanie kilku instrukcji (ang. ILP
 instruction level parallelism) w jednym cyklu zegarowym; dwa istotne czynniki
ograniczają równoległość wykonywania:
" zle przewidziane rozgałęzienia (skoki);
" relatywnie wysokie opóznienie związane z ładowaniem danych z pamięci;
f& w wielu współczesnych procesorach algorytmy przewidywania rozgałęzień mają na celu
określenie czy pewien konkretny skok w programie zostanie wykonany; trafne
przewidywanie rozgałęzień pozwala procesorowi określić wcześniej przepływ sterowania,
i pobierać te instrukcje, które znajdują się w ścieżce wykonania;
f& w praktyce dokładne przewidywanie rozgałęzień może być trudne, i często wymaga
pewnych form analizy sprzężenia zwrotnego, by stała się efektywna; błędne
przewidywanie powoduje opóznienia sięgające do 10 cykli zegara;
f& niezależnie od problemów związanych ze zle przewidzianymi rozgałęzieniami, obecność
rozkazów sterujących (skokowych) może ograniczać możliwości optymalizacji kodu
poprzez przestawianie instrukcji;
f& w ostatnich latach skupiono więc uwagę na sposobach kodowania, w których nie używa
się (lub ogranicza) rozkazów skoku warunkowego; omawiany problem wyjaśnimy na
przykładzie;
f& przypuśćmy, że w rejestrach ESI i EDI znajdują się dwie 32-bitowe liczby całkowite bez
znaku, a zadanie polega na wyświetleniu na ekranie większej z tych liczb;
konwencjonalne rozwiązanie mogłoby wyglądać tak:
mov eax, esi
cmp esi, edi
jae dalej
mov eax, edi
dalej: call wyswietl32 ; wyświetlanie zawartości EAX
f& poszukiwaną wartość można wyznaczyć także w inny sposób; przyjmijmy, że 32-bitowa
zmienna b może przyjmować wartości 00...000 (false) albo 11...111 (true); algorytm
obliczeń, w którym nie używa się skoków, można zapisać w postaci:
b = ESI > EDI
wynik = ESI & b + EDI & (~ b)
print (wynik)
gdzie symbole & i ~ oznaczajÄ…:
& bitowy iloczyn logiczny
~ negacja bitowa
f& podany algorytm implementuje poniższa sekwencja rozkazów:
mov edx, 0 ; zmienna b
cmp esi, edi
setg dl ; wpisanie 1 do DL, jeśli ESI > EDI
neg edx ; zmiana znaku liczby kodowanej w U2
; jeśli liczba w ESI była większa od liczby w EDI, to w EDX
; (zmienna b) będą same jedynki, w przeciwnym razie same zera
and esi, edx
not edx
and edi, edx
or esi, edi
mov eax, esi
call wyswietl32 ; wyświetlanie zawartości EAX
f& warto dodać, że rozkaz SETG można zastąpić kilkoma typowymi rozkazami, które
wykonujÄ… operacje bitowe i arytmetyczne na znacznikach CF i ZF odczytanych za
pośrednictwem stosu (rozkaz PUSHF);
f& przedstawiona tu metoda programowania, nazywana metodÄ… predykatowÄ… (ang.
predication), jest wspomagana sprzętowo w najnowszych typach procesorów, m.in. w 64-
bitowym procesorze Itanium.
Metoda predykatowa w procesorze Itanium
f& w procesorach IA-64 udostępniono mechanizm predykatowego wykonywania (ang.
predicated execution) instrukcji, który pozwala na całkowite wyeliminowanie rozgałęzień
w programie; wymaga to jednak stosowania innej techniki programowania, którą ilustruje
poniższy przykład
if (x > 5) printf("x > 5\n");
else printf("x <= 5\n");
f& najpierw, na podstawie porównania zmiennej x i liczby 5 obliczane są wartości zmiennych
boolowskich b1 i b2; jeśli x > 5, to b1 przyjmuje wartość true, w przeciwnym razie
false; analogicznie jeśli x > 5, to b2 przyjmuje wartość false, a przeciwnym razie true;
b1, b2 = cmp.gt x,5 // obliczenie zmiennych
// boolowskich b1 i b2 na
// podstawie porównania x i 5
(b1) call printf "x > 5\n" // instrukcja jest
// wykon. gdy b1=true
(b2) call printf "x <= 5\n" // instrukcja jest
// wykon. gdy b2=true
f& instrukcje call wykonywane są tylko wówczas, jeśli wyrażenie podane w nawiasie ma
wartość true; zatem jeśli zmienna b1 ma wartość true, to wykonywana jest instrukcja
call, która wyświetla tekst "x > 5\n"; analogicznie jeśli zmienna b2 ma wartość true, to
wykonywana jest druga instrukcja call;
f& zatem, używanie wartości boolowskich (predykatów) pozwala przekształcić instrukcje if-
then-else na kod bez rozgałęzień; transformacja ta nazywana jest if-konwersją (ang. if-
conversion);
f& procesory IA-64 posiadajÄ… rozbudowane mechanizmy wykonywania kodu
predykatowego; istnieją liczne instrukcje porównania, które pozwalają na tworzenie
wartości logicznych; prawie wszystkie instrukcje IA-64 mogą włączane i wyłączane
poprzez predykaty z odpowiednimi wartościami;
f& nadmierne stosowanie omawianej techniki może prowadzić do zwiększenia długości kodu
 technikę tę trzeba wprowadzać ostrożnie.
Optymalizacja kodu programu
f& w zastosowaniach wymagających bardzo dużej prędkości przetwarzania (np. sterowanie
poruszającymi się obiektami, poszukiwania w wielkich bazach danych) niektóre,
krytyczne fragmenty programu koduje się na poziomie rozkazów procesora; w takich
przypadkach wskazane jest umieszczenie rozkazów w takiej kolejności, aby
wyeliminować konflikty wynikające ze specyfiki przetwarzania potokowego;
f& dostępna jest dość obszerna literatura poświęcona temu zagadnieniu, ale proponowane
rozwiązania są ściśle uzależnione od architektury używanego procesora;
f& spośród najczęściej podawanych zaleceń można podać:
obliczanie wartości modyfikatora z wyprzedzeniem,
unikanie modyfikacji adresowej z użyciem dwóch modyfikatorów czy też
współczynnika skali,
f& przykład: obliczenie wartości wyrażenia J := K + M + N + P; rozwiązanie podane w
prawej kolumnie realizowane jest przez dwie jednostki wykonawcze procesora, a zatem
wymaga mniej cykli procesora.
mov ax, K mov bx, K
add ax, M mov ax, M
add ax, N add bx, N
add ax, P add ax, P
mov J, ax add ax, bx
mov J, ax
Problemy dostępu do pamięci
f& dostęp do pamięci we współczesnych komputerach wymaga relatywnie długiego czasu w
porównaniu z czasem dekodowania i wykonywania instrukcji; znaczna poprawa w tym
zakresie nastąpiła po wprowadzeniu pamięci podręcznej w procesorze, ale także i w tym
przypadku potrzeba kilku cykli procesora;
f& jednym ze sposobów rozwiązania tego problemu jest odczytywanie danych z pamięci z
wyprzedzeniem; w trakcie analizy poniższego kodu można przyjąć założenie, że
wykonanie instrukcji zajmuje 1 cykl, ale załadowanie danych z pamięci podręcznej
zajmuje 3 cykle; zatem wykonanie podanego niżej kodu zajmuje 11 cykli;
r16 = call rand ( )
add r16 = r16, 2
mul r16 = r16, 4
st8 [r16] = 2
ld8 r17 = addr("x") // wyznaczanie adresu "x"
ld8 r18 = [r17] // ładowanie zmiennej x z pamięci
add r18 = r18, 1
st8 [r17] = r18 // zapisywanie x do pamięci
f& w trakcie wykonywania powyższych instrukcji występuje oczekiwanie procesora przez 3
cykle; niżej podany kod wymaga jednak tylko 8 cykli;
r16 = call rand ( )
ld8 r17 = addr("x") // wyznaczanie adresu "x"
ld8 r18 = [r17] // ładowanie zmiennej x z pamięci
add r16 = r16, 2
mul r16 = r16, 4
st8 [r16] = 2
add r18 = r18, 1
st8 [r17] = r18 // zapisywanie x do pamięci


Wyszukiwarka